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


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

Пример 1:

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

Пример 2:

class A {
    B b;

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

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 (и для объяснений по блокам инициализации и другим не очень известным функциям инициализации).

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

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

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

оба метода являются приемлемыми. Обратите внимание, что в последнем случае 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>) из любой точки вашего кода. Затем вы сразу увидите значение этой переменной. В противном случае вам нужно "искать" место, где выполняется инициализация (в основном: конструктор).

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