Должен ли я создавать экземпляры переменных экземпляра при объявлении или в конструкторе?
есть ли какие-либо преимущества для любого подхода?
Пример 1:
class A {
B b = new B();
}
Пример 2:
class A {
B b;
A() {
b = new B();
}
}
13 ответов:
- нет никакой разницы-инициализация переменной экземпляра фактически помещается в конструктор(ы) компилятором.
- первый вариант более читабельный.
- вы не можете иметь обработку исключений с первого варианта.
есть дополнительно блок инициализации, который также помещается в конструктор(ы) компилятором:
{ a = new A(); }
Регистрация объяснение Солнца и совет
С в этом уроке:
объявления полей, однако, не являются частью какого-либо метода, поэтому они не могут быть выполнены как операторы. Вместо этого компилятор Java автоматически генерирует код инициализации поля экземпляра и помещает его в конструктор или конструкторы для класса. Код инициализации вставляется в конструктор в том порядке, в котором он отображается в исходном коде, что означает, что инициализатор поля может используйте начальные значения полей, объявленных перед ним.
кроме того, вы можете захотеть лениво инициализировать ваше поле. В тех случаях, когда инициализация поля является дорогостоящей операцией, вы можете инициализировать его, как только он нужен:
ExpensiveObject o; public ExpensiveObject getExpensiveObject() { if (o == null) { o = new ExpensiveObject(); } return o; }
и в конечном счете (как указал Билл), ради управления зависимостями лучше избежать С помощью
new
оператор в любом месте вашего класса. Вместо этого, используя Инъекции Зависимостей предпочтительнее-т. е. позволяя кому-то другому (другой класс/фреймворк) создавать экземпляры и вводить зависимости в ваш класс.
другой вариант - использовать Инъекции Зависимостей.
class A{ B b; A(B b) { this.b = b; } }
это снимает ответственность за создание
B
объект из конструктораA
. Это сделает ваш код более тестируемым и простым в обслуживании в долгосрочной перспективе. Идея состоит в том, чтобы уменьшить связь между двумя классами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
инициализатор вызывается после вызывается конструктор суперкласса. Поскольку конструктор суперкласса вызывает init (), инициализацияa
и следовал наa = null
инициализации.
мое личное "правило" (почти никогда не нарушается) заключается в следующем:
- объявлять все переменные в начале блок
- сделать все переменные окончательными, если они не может быть
- объявить одну переменную в строке
- никогда не инициализировать переменную, в которой объявлено
- только инициализировать что-то в a конструктор, когда ему нужны данные из конструктор для выполнения инициализация
Так что у меня был бы код например:
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% уверен, где искать объявления переменных (в начале блока) и их назначения (как только это имеет смысл после объявления). Это также потенциально более эффективно, поскольку вы никогда не инициализируете переменную со значением, которое не используется (например, declare и init vars, а затем бросаете исключение до половины тех vars, которые должны иметь значение). Вы также не закончите делать бессмысленно инициализация (например, int i = 0; а затем позже, до использования "i", do 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 там, вы получите null b только при использовании этого последнего конструктора.
см.http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html для получения более подробной информации о инициализация в Java (и для объяснений по блокам инициализации и другим не очень известным функциям инициализации).
используя инъекции зависимостей или ленивая инициализация всегда предпочтительнее, как уже подробно объяснялось в других ответах.
когда вы не хотите или не можете использовать эти шаблоны и для примитивных типов данных, есть три веские причины, по которым я могу думать о том, почему предпочтительнее инициализировать атрибуты класса вне конструктора:
- избежать повторения = если у вас есть более чем одна конструктор, или когда вам нужно будет добавить больше, вам не придется повторять инициализацию снова и снова во всех конструкторах тел;
- улучшена читаемость = вы можете легко сказать, с первого взгляда, какие переменные должны быть инициализированы вне класса;
- сокращенные строки кода = для каждой инициализации, выполненной в объявлении, в конструкторе будет меньше строки.
оба метода являются приемлемыми. Обратите внимание, что в последнем случае
b=new B()
может не инициализироваться, если присутствует другой конструктор. Подумайте о коде инициализатора вне конструктора как об общем конструкторе, и код выполняется.
Я думаю, что Пример 2 Предпочтительнее. Я думаю, что лучше всего объявить вне конструктора и инициализировать в конструкторе.
второй пример ленивой инициализации. Во-первых, это более простая инициализация, они по сути одинаковы.
есть еще одна тонкая причина для инициализации вне конструктора, о которой никто не упоминал ранее (очень конкретный, я должен сказать). Если вы используете инструменты UML для создания диаграмм классов из кода (обратное проектирование), большинство инструментов, которые я считаю, отметят инициализацию примера 1 и перенесут его на диаграмму (если вы предпочитаете показывать начальные значения, как я). Они не будут принимать эти начальные значения из примера 2. Опять же, это очень конкретная причина-если вы работаете с инструментами UML, но как только я узнал об этом, я пытаюсь взять все свои значения по умолчанию за пределами конструктора, если, как упоминалось ранее, нет проблемы возможного исключения или сложной логики.
второй вариант предпочтительнее, так как позволяет использовать другую логику в ctors для создания экземпляра класса и использовать цепочку ctors. Е. Г.
class A { int b; // secondary ctor A(String b) { this(Integer.valueOf(b)); } // primary ctor A(int b) { this.b = b; } }
таким образом, второй вариант является более гибким.
Я не видел в ответах:
возможное преимущество наличия инициализации во время объявления может быть в настоящее время IDE, где вы можете очень легко перейти к объявлению переменной (в основном
Ctrl-<hover_over_the_variable>-<left_mouse_click>
) из любой точки вашего кода. Затем вы сразу увидите значение этой переменной. В противном случае вам нужно "искать" место, где выполняется инициализация (в основном: конструктор).Это преимущество конечно вторично по отношению ко всем другим логическим рассуждениям, но для некоторых людей эта "особенность" может быть более важной.