Выбор, когда создавать экземпляры классов - PullRequest
1 голос
/ 15 ноября 2008

Недавно я написал класс для назначения, в котором мне нужно было хранить имена в ArrayList (в Java). Я инициализировал ArrayList как переменную экземпляра private ArrayList<String> names. Позже, когда я проверил свою работу на соответствие решению, я заметил, что они инициализировали свой ArrayList в методе run().

Я немного подумал над этим и чувствовал, что это может быть дело вкуса, но в целом, как выбрать в таких ситуациях? Один занимает меньше памяти или что-то?

PS Мне нравятся переменные экземпляра в Ruby, которые начинаются с символа @: они красивее.

(мета-вопрос: как лучше подойти к этому вопросу?)

Ответы [ 6 ]

3 голосов
/ 15 ноября 2008

По словам великого Кнута, «Преждевременная оптимизация - корень всего зла».

Просто беспокойтесь, что ваша программа работает правильно и в ней нет ошибок. Это гораздо важнее неясной оптимизации, которую потом будет сложно отлаживать.

Но чтобы ответить на ваш вопрос - если вы инициализируете в элементе класса, память будет выделяться при первом упоминании вашего класса в коде (т. Е. Когда вы вызываете метод из него). Если вы инициализируете в методе, выделение памяти происходит позже, когда вы вызываете этот конкретный метод.

Так что это только вопрос инициализации позже ... это называется ленивой инициализацией в отрасли.

2 голосов
/ 15 ноября 2008

инициализация

Как правило, попробуйте инициализировать переменные, когда они объявлены.

Если значение переменной не предназначено для изменения, сделайте это явным, используя ключевое слово final. Это поможет вам понять правильность вашего кода, и хотя я не знаю об оптимизации компилятора или JVM, которая распознает ключевое слово final, они, безусловно, были бы возможны.

Конечно, есть исключения из этого правила. Например, переменная может быть назначена в if - else или переключателе. В таком случае «пустое» объявление (без инициализации) предпочтительнее инициализации, которая гарантированно будет перезаписана до считывания фиктивного значения.

/* DON'T DO THIS! */
Color color = null;
switch(colorCode) {
  case RED: color = new Color("crimson"); break;
  case GREEN: color = new Color("lime"); break;
  case BLUE: color = new Color("azure"); break;
}
color.fill(widget);

Теперь у вас есть NullPointerException, если представлен нераспознанный цветовой код. Было бы лучше не назначать бессмысленные null. Компилятор выдаст ошибку при вызове color.fill(), потому что он обнаружит, что вы, возможно, не инициализировали color.

Чтобы ответить на ваш вопрос в этом случае, мне нужно увидеть соответствующий код. Если решение инициализировало его внутри метода run(), оно должно было использоваться либо как временное хранилище, либо как способ «возврата» результатов задачи.

Если коллекция используется как временное хранилище и недоступна вне метода, она должна быть объявлена ​​как локальная переменная, а не переменная экземпляра, и, скорее всего, должна быть инициализирована там, где она объявлена ​​в методе.

Проблемы параллелизма

На начальном курсе программирования ваш инструктор, вероятно, не пытался столкнуть вас со сложностями параллельного программирования - хотя, если это так, я не уверен, почему вы использовали Thread. Но с учетом современных тенденций в разработке процессоров каждый, кто учится программировать, должен иметь четкое представление о параллелизме. Я попытаюсь вникнуть немного глубже.

Возвращать результаты из метода run потока немного сложно. Этот метод является интерфейсом Runnable , и ничто не мешает нескольким потокам выполнить метод run одного экземпляра. Возникшие в результате проблемы параллелизма являются частью мотивации интерфейса Callable , представленного в Java 5. Он очень похож на Runnable, но может возвращать результат в поточно-ориентированном режиме и выдает Exception, если задание не может быть выполнено.

Это немного отступает, но если вам интересно, рассмотрите следующий пример:

class Oops extends Thread { /* Note that thread implements "Runnable" */

  private int counter = 0;

  private Collection<Integer> state = ...;

  public void run() {
    state.add(counter);
    counter++;
  }

  public static void main(String... argv) throws Exception {
    Oops oops = new Oops();
    oops.start();
    Thread t2 = new Thread(oops); /* Now pass the same Runnable to a new Thread. */
    t2.start(); /* Execute the "run" method of the same instance again. */
    ...
  }
}

К концу метода main вы почти не представляете, что такое "состояние" Collection. Два потока работают над этим одновременно, и мы не указали, является ли коллекция безопасной для одновременного использования. Если мы инициализируем его внутри потока, по крайней мере, мы можем сказать, что в конечном итоге state будет содержать один элемент, но мы не можем сказать, 0 или 1.

0 голосов
/ 21 ноября 2008

Вы должны избегать отложенной инициализации. Это приводит к проблемам позже.
Но если вам нужно сделать это из-за слишком тяжелой инициализации, вы должны сделать это так:

Статические поля:

// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
   static final FieldType field = computeFieldValue();
}
static FieldType getField() { return FieldHolder.field; }

Поля экземпляра:

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
   FieldType result = field;
   if (result == null) { // First check (no locking)
      synchronized(this) {
         result = field;
         if (result == null) // Second check (with locking)
            field = result = computeFieldValue();
      }
   }
   return result;
}

Согласно книге Джошуа Бола "Эффективная Ява ™" Второе издание »(ISBN-13: 978-0-321-35668-0):
«Разумно использовать ленивую инициализацию»

0 голосов
/ 15 ноября 2008

Мое личное правило для переменных экземпляра - инициализировать их, по крайней мере, со значением по умолчанию, либо:

  1. во время делкарации, т.е.

    private ArrayList<String> myStrings = new ArrayList<String>();

  2. в конструкторе

Если это что-то, что действительно является переменной экземпляра и представляет состояние объекта, то оно полностью инициализируется ко времени выхода из конструктора. В противном случае вы открываете себе возможность доступа к переменной до того, как она получит значение. Конечно, это не относится к примитивам, где вы получите значение по умолчанию автоматически.

Для статических (на уровне класса) переменных инициализируйте их в объявлении или в статическом инициализаторе. Я использую статический инициализатор, если у меня есть расчеты или другая работа, чтобы получить значение. Инициализируйте в объявлении, если вы просто вызываете new Foo() или устанавливаете переменную в известное значение.

0 голосов
/ 15 ноября 2008

Я не совсем понимаю вашу полную проблему.

Но, насколько я понимаю, выигрыш в производительности / памяти будет довольно незначительным. Поэтому я определенно предпочел бы сторону легкости.

Так что делай то, что подходит тебе лучше всего. Обращайтесь к производительности / оптимизации памяти только при необходимости.

0 голосов
/ 15 ноября 2008

из вики-книги :

Существует три основных вида области видимости для переменных в Java:

  • локальная переменная, объявленная в методе класса, действительная (и занимающая только хранилище) время выполнения метода. Каждый раз, когда вызывается метод, используется новая копия переменной.

  • переменная экземпляра, объявленная внутри класса, но вне любого метода. Он действителен и занимает хранилище до тех пор, пока соответствующий объект находится в памяти; программа может создавать несколько объектов класса, и каждый получает свою собственную копию всех переменных экземпляра. Это основное правило структуры данных объектно-ориентированного программирования; классы определены для хранения данных, специфичных для «класса объектов» в данной системе, и каждый экземпляр содержит свои собственные данные.

  • статическая переменная, объявленная внутри класса как статическая, вне любого метода. Существует только одна копия такой переменной, независимо от того, сколько объектов создано из этого класса.

Так что да, потребление памяти является проблемой, особенно если ArrayList внутри run () является локальным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...