Есть ли какое-либо преимущество для любого подхода?
Пример 1:
class A {
B b = new B();
}
Пример 2:
class A {
B b;
A() {
b = new B();
}
}
Есть ли какое-либо преимущество для любого подхода?
Пример 1:
class A {
B b = new B();
}
Пример 2:
class A {
B b;
A() {
b = new B();
}
}
Существует также блок инициализации, который также помещается в конструктор компилятором:
{
a = new A();
}
Отметьте объяснение и советы Sun
Из этот учебник:
Полевые декларации, однако, не являются частью какого-либо метода, поэтому они не могут быть выполнены в качестве операторов. Вместо этого компилятор Java автоматически генерирует код инициализации поля экземпляра и помещает его в конструктор или конструкторы для класса. Код инициализации вставляется в конструктор в порядке, указанном в исходном коде, что означает, что инициализатор поля может использовать начальные значения объявленных перед ним полей.
Кроме того, вы можете лениво инициализировать свое поле. В случаях, когда инициализация поля является дорогостоящей операцией, вы можете инициализировать ее, как только она понадобится:
ExpensiveObject o;
public ExpensiveObject getExpensiveObject() {
if (o == null) {
o = new ExpensiveObject();
}
return o;
}
И в конечном счете (как указано Биллом), ради управления зависимостями, лучше избегать использования оператора new
в любом месте вашего класса. Вместо этого рекомендуется использовать Injection of Dependency, то есть позволить кому-то другому (другому классу/фреймворку) создавать экземпляры и вставлять зависимости в вашем классе.
Другой вариант - использовать Injection Dependency.
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
.
мое личное "правило" (вряд ли когда-либо сломано) заключается в следующем:
Итак, у меня был бы код вроде:
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 я = 0, а затем позже, прежде чем использовать "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 a subclass of RuntimeException.
}
}
// ...
}
Я считаю, что это почти просто вопрос вкуса, если инициализация проста и не нуждается в какой-либо логике.
Подход конструктора является немного более хрупким, если вы не используете блок инициализатора, потому что если позже вы добавите второй конструктор и забудете инициализировать b там, вы получите нуль b только при использовании этого последнего конструктора.
Подробнее о инициализации в Java см. http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html и объяснения по блокам инициализатора и другим неизвестным функциям инициализации.
Использование либо инъекции зависимостей, либо ленивой инициализации всегда предпочтительнее, как уже подробно объяснено в других ответах.
Если вы не хотите или не можете использовать эти шаблоны и для примитивных типов данных, есть три веские причины, по которым я могу подумать, почему предпочтительнее инициализировать атрибуты класса вне конструктора:
Оба метода приемлемы. Обратите внимание, что в последнем случае b=new B()
не может быть инициализирован, если присутствует другой конструктор. Подумайте о инициализационном коде вне конструктора как об общем конструкторе, и код будет выполнен.
Я думаю, что пример 2 предпочтительнее. Я считаю, что лучше всего объявить вне конструктора и инициализировать конструктор.
Я не видел следующее в ответах:
Возможное преимущество инициализации во время объявления может быть в современной IDE, где вы можете очень легко перейти к объявлению переменной (в основном Ctrl-<hover_over_the_variable>-<left_mouse_click>
) из любой точки вашего кода. Затем вы сразу увидите значение этой переменной. В противном случае вам придется "искать" место, где выполняется инициализация (в основном: конструктор).
Это преимущество, конечно, вторично по отношению ко всем другим логическим рассуждениям, но для некоторых людей эта "особенность" может быть более важной.
Второй пример - ленивая инициализация. Первая - более простая инициализация, они по существу одинаковы.
Есть еще одна тонкая причина для инициализации вне конструктора, о которой никто не упоминал раньше (очень конкретный должен сказать). Если вы используете инструменты 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;
}
}
Таким образом, второй вариант более гибкий.
На самом деле это совсем другое:
Декларация происходит до начала строительства. Так, скажем, если кто-то инициализировал переменную (в данном случае b) в обоих местах, инициализация конструктора заменит инициализацию на уровне класса.
Поэтому объявляйте переменные на уровне класса, инициализируйте их в конструкторе.
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
Что касается вышеизложенного,
String a = null;
null init можно избежать, так как в любом случае это значение по умолчанию. Однако, если вам нужно другое значение по умолчанию, затем, из-за неконтролируемого порядка инициализации, Я бы исправил это следующим образом:
class MyClass extends FooClass
{
String a;
{
if( a==null ) a="my custom default value";
}
...