Выбор, когда создавать экземпляры классов

1

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

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

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

(мета-вопрос: что было бы лучшим заголовком для этого вопроса?)

  • 0
    Возможные мета-ответы: «Выбор, когда инициализировать поля», «Когда использовать инициализацию ленивых полей»
Теги:
performance
variables

6 ответов

3
Лучший ответ

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

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

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

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

  • 0
    Я не уверен, что это просто ленивая инициализация или использование переменной экземпляра вместо локальной переменной.
2

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

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

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

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

/* 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(), оно должно было использоваться либо как временное хранилище, либо как способ "вернуть" результаты задачи.

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

Concurrency Проблемы

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

Возвращаемые результаты из потока run метод немного сложнее. Этот метод представляет собой Runnable интерфейс, и ничто не останавливает несколько потоков от выполнения метода run одного экземпляра. Результирующие проблемы concurrency являются частью мотивации интерфейса 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
    Не уверен, что на самом деле постер использует тему. Метод, который он использует только что, может быть, называется "запустить"?
  • 0
    Хороший вопрос, может быть ...
0

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

  • во время деления, т.е.

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

  • в конструкторе

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

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

0

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

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

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

0

От wikibooks:

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

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

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

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

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

-2

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

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

// 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;
}

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

  • 0
    en.wikipedia.org/wiki/Double-checked_locking, чтобы поддержать мою претензию "не совсем поточно-ориентированный".
  • 0
    Я исправляю помехи. По поводу претензии: Значит, Джошуа Болч не прав? В википедии говорится: «Начиная с J2SE 5.0 эта проблема была исправлена. Теперь ключевое слово volatile гарантирует, что несколько потоков правильно обрабатывают экземпляр singleton».
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню