it-roy-ru.com

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

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

Пример 1:

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

Пример 2:

class A {
    B b;

    A() {
         b = new B();
    }
}
204
DonX
  • Нет никакой разницы - инициализация переменной экземпляра фактически помещается в конструктор (ы) компилятором.
  • Первый вариант более читабелен.
  • Вы не можете иметь обработку исключений с первым вариантом.
  • Кроме того, имеется блок инициализации, который также помещается в конструктор (ы) компилятором:

    {
        a = new A();
    }
    

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

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

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

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

ExpensiveObject o;

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

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

250
Bozho

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

class A{
   B b;

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

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

35
Bill the Lizard

Я сгорел интересным способом сегодня:

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.

16
Edward Falk

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

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

Так что я хотел бы иметь такой код:

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% уверен, где искать объявления переменных (в начале блока) и их присваивания (как только это имеет смысл после объявления). Это также потенциально может быть более эффективным, поскольку вы никогда не инициализируете переменную значением, которое не используется (например, объявляете и инициализируете переменные, а затем генерируете исключение до того, как половина этих переменных должна иметь значение). Вы также не будете выполнять бессмысленную инициализацию (например, int i = 0; затем, прежде чем использовать «i», сделайте i = 5 ;.

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

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

14
TofuBeer

Пример 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.
        }
    }

    // ...

}
7
BalusC

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

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

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

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

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

См. http://Java.Sun.com/docs/books/tutorial/Java/javaOO/initial.html для получения более подробной информации об инициализации в Java (и для объяснения блоков инициализатора и других не очень известных функций инициализации) ,.

4
Vinko Vrsalovic

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

1
Chandra Patni

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

1
jkeesh

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

0
tulu

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

class A {
    int b;

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

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

Так что второй вариант более гибкий.

0
Andriy Kryvtsun

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

0
Imad S

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

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

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

0
GeertVc

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

0
fastcodejava