Должен ли я создавать экземпляры переменных экземпляра в объявлении или в конструкторе?

Есть ли какое-либо преимущество для любого подхода?

Пример 1:

class A { B b = new B(); } 

Пример 2:

 class A { B b; A() { b = new B(); } } 

  • Нет никакой разницы – инициализация переменной экземпляра фактически помещается в конструктор (ы) компилятором.
  • Первый вариант более читабельен.
  • У вас не может быть обработки исключений с первым вариантом.
  • Существует также блок инициализации, который также помещается в конструктор (ы) компилятором:

     { a = new A(); } 

Проверьте объяснения и советы Sun

Из этого урока :

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

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

 ExpensiveObject o; public ExpensiveObject getExpensiveObject() { if (o == null) { o = new ExpensiveObject(); } return o; } 

И в конечном счете (как отметил Билл), для управления зависимостью, лучше избегать использования new оператора в любом месте вашего classа. Вместо этого использование Injection Dependency предпочтительнее, то есть позволить кому-то другому (другому classу / фреймворку) создавать экземпляры и вводить зависимости в вашем classе.

Другой вариант – использовать Injection Dependency .

 class A{ B b; A(B b) { this.b = b; } } 

Это снимает ответственность за создание объекта B из конструктора A Это сделает ваш код более надежным и более легким в обслуживании в долгосрочной перспективе. Идея состоит в том, чтобы уменьшить связь между двумя classами A и B Преимущество, которое это дает вам, заключается в том, что теперь вы можете передать любой объект, который расширяет B (или реализует B если это интерфейс) для конструктора A , и он будет работать. Одним из недостатков является то, что вы отказываетесь от инкапсуляции объекта B , поэтому он подвергается вызову конструктора A Вам придется подумать, стоит ли выигрывать этот компромисс, но во многих случаях они есть.

Сегодня меня сожгли интересным образом:

 class MyClass extends FooClass { String a = null; public MyClass() { super(); // Superclass calls init(); } @Override protected void init() { super.init(); if (something) a = getStringYadaYada(); } } 

См. Ошибку? Оказывается, инициализатор a = null вызывается после вызова конструктора суперclassа. Поскольку конструктор суперclassа вызывает init (), за инициализацией a следует инициализация a = null .

мое личное «правило» (почти никогда не сломанное) заключается в следующем:

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

Поэтому у меня был бы код вроде:

 public class X { public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me private static final int A; private final int b; private int c; static { A = 42; } { b = 7; } public X(final int val) { c = val; } public void foo(final boolean f) { final int d; final int e; d = 7; // I will eat my own eyes before using ?: - personal taste. if(f) { e = 1; } else { e = 2; } } } 

Таким образом, я всегда на 100% уверен, где искать объявления переменных (в начале блока) и их назначения (как только это имеет смысл после объявления). Это приводит к повышению эффективности, поскольку вы никогда не инициализируете переменную со значением, которое не используется (например, объявлять и инициализировать vars, а затем выдавать исключение, прежде чем половина этих варов должна иметь значение). Вы также не завершаете бессмысленную инициализацию (например, int i = 0, а затем позже, прежде чем использовать «i», выполните i = 5 ;.

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

Ваш пробег может отличаться.

Пример 2 менее гибкий. Если вы добавите другой конструктор, вам также нужно запомнить экземпляр поля в этом конструкторе. Просто создайте экземпляр поля непосредственно или введите ленивую загрузку где-нибудь в геттере.

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

 public class A { private Properties properties; { try { properties = new Properties(); properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties")); } catch (IOException e) { throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException. } } // ... } 

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

Подход конструктора является немного более хрупким, если вы не используете блок инициализатора, потому что если позже вы добавите второй конструктор и забудете инициализировать b там, вы получите нуль b только при использовании этого последнего конструктора.

Дополнительную информацию об инициализации в Java см. На странице http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html (и для пояснения по блокам инициализатора и другим малоизвестным функциям инициализации).

Использование либо инъекции зависимостей, либо ленивой инициализации всегда предпочтительнее, как уже подробно объяснено в других ответах.

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

  1. избегайте повторения = если у вас более одного конструктора, или когда вам нужно будет добавить больше, вам не придется повторять инициализацию снова и снова во всех телах конструкторов;
  2. улучшенная читаемость = вы можете легко с первого взгляда определить, какие переменные должны быть инициализированы вне classа;
  3. сокращенные строки кода = для каждой инициализации, сделанной при объявлении, в конструкторе будет меньше строки.

Оба метода приемлемы. Заметим, что в последнем случае b=new B() не может быть инициализирован, если присутствует другой конструктор. Подумайте о инициализационном коде вне конструктора как об общем конструкторе, и код будет выполнен.

Я думаю, что пример 2 предпочтительнее. Я считаю, что лучшей практикой является объявление вне конструктора и инициализация в конструкторе.

Второй пример – ленивая инициализация. Первая – более простая инициализация, они по существу одинаковы.

Есть еще одна тонкая причина для инициализации вне конструктора, о которой никто не упомянул раньше (очень конкретный должен сказать). Если вы используете инструменты UML для создания диаграмм classов из кода (обратное проектирование), большинство инструментов, которые, как я полагаю, заметят инициализацию примера 1 и передадут его на диаграмму (если вы предпочитаете, чтобы он отображал начальные значения, например Я делаю). Они не будут принимать эти начальные значения из примера 2. Опять же, это очень конкретная причина – если вы работаете с инструментами UML, но как только я узнал об этом, я пытаюсь использовать все значения по умолчанию вне конструктора, если, как и было упомянутый ранее, существует проблема возможного выброса или сложной логики.

Второй вариант предпочтительнее, так как позволяет использовать различную логику в ctors для создания экземпляров classа и использовать цепочку ctors. Например

 class A { int b; // secondary ctor A(String b) { this(Integer.valueOf(b)); } // primary ctor A(int b) { this.b = b; } } 

Таким образом, второй вариант более гибкий.

Я не видел в ответах следующее:

Возможное преимущество наличия инициализации во время объявления может быть в настоящее время IDE, где вы можете очень легко перейти к объявлению переменной (в основном Ctrl-- ) из любого места вашего кода. Затем вы сразу увидите значение этой переменной. В противном случае вам нужно «искать» место, где выполняется инициализация (в основном: конструктор).

Это преимущество, конечно, вторично по отношению ко всем другим логическим рассуждениям, но для некоторых людей «особенность» может быть более важной.

Давайте будем гением компьютера.