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

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

Пример 1:

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

Пример 2:

class A {
    B b;

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

Ответ 1

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

    {
        a = new A();
    }
    

Отметьте объяснение и советы Sun

Из этот учебник:

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

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

ExpensiveObject o;

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

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

Ответ 2

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

class A{
   B b;

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

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

Ответ 3

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

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.

Ответ 4

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

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

Итак, у меня был бы код вроде:

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;.

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

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

Ответ 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.
        }
    }

    // ...

}

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

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

Ответ 11

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

Ответ 12

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

Ответ 13

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

class A {
    int b;

    // secondary ctor
    A(String b) {
         this(Integer.valueOf(b));
    }

    // primary ctor
    A(int b) {
         this.b = b;
    }
}

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

Ответ 14

На самом деле это совсем другое:

Декларация происходит до начала строительства. Так, скажем, если кто-то инициализировал переменную (в данном случае b) в обоих местах, инициализация конструктора заменит инициализацию на уровне класса.

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

Ответ 15

    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";
    }
    ...