Использование инициализаторов и конструкторов в Java - PullRequest
91 голосов
/ 30 апреля 2009

Итак, в последнее время я совершенствовался в своих навыках Java и обнаружил несколько функций, о которых раньше не знал. Статические инициализаторы и инициализаторы являются двумя такими методами.

Мой вопрос: когда использовать инициализатор вместо включения кода в конструктор? Я подумал о паре очевидных возможностей:

  • Инициализаторы статического / экземпляра можно использовать для установки значения «окончательных» статических / экземпляровых переменных, тогда как конструктор не может

  • статические инициализаторы могут использоваться для установки значения любых статических переменных в классе, что должно быть более эффективным, чем наличие блока кода "if (someStaticVar == null) // do stuff" в начале каждого конструктора

В обоих этих случаях предполагается, что код, требуемый для установки этих переменных, является более сложным, чем просто «var = value», так как в противном случае не было бы никакой причины использовать инициализатор вместо простой установки значения при объявлении переменная.

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

Можно, конечно, использовать инициализатор для большей части того, что делается в конструкторе, но я действительно не вижу причины для этого. Даже если все конструкторы класса совместно используют большой объем кода, использование закрытой функции initialize () представляется мне более целесообразным, чем использование инициализатора, поскольку он не блокирует выполнение этого кода при написании нового конструктор.

Я что-то упустил? Есть ли ряд других ситуаций, в которых следует использовать инициализатор? Или это действительно довольно ограниченный инструмент для использования в очень специфических ситуациях?

Ответы [ 9 ]

53 голосов
/ 30 апреля 2009

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

Я нахожу "if (someStaticVar == null) // делать вещи" грязно и подвержено ошибкам. Если он инициализируется статически и объявляется final, то вы исключаете возможность того, что он будет null.

Однако я смущен, когда вы говорите:

Инициализаторы static / instance можно использовать для установки значения final статические переменные / переменные экземпляра, тогда как конструктор не может

Полагаю, вы говорите оба:

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

и вы правы по первому пункту, неправильно по второму. Вы можете, например, сделать это:

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

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

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}
50 голосов
/ 30 апреля 2009

Анонимные внутренние классы не могут иметь конструктора (так как они анонимны), поэтому они вполне естественно подходят для инициализаторов экземпляров.

26 голосов
/ 30 апреля 2009

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

public class Deck {
  private final static List<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    list.add("Clubs");
    list.add("Spades");
    list.add("Hearts");
    list.add("Diamonds");
    SUITS = Collections.unmodifiableList(list);
  }

  ...
}

Теперь этот пример можно сделать с помощью одной строки кода:

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

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

Наивная реализация также не может создавать неизменяемый список, что является потенциальной ошибкой. Вышеописанное создает неизменную структуру данных, которую вы можете с радостью вернуть из открытых методов и т. Д.

14 голосов
/ 30 апреля 2009

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

public class MyClass {

    static private Properties propTable;

    static
    {
        try 
        {
            propTable.load(new FileInputStream("/data/user.prop"));
        } 
        catch (Exception e) 
        {
            propTable.put("user", System.getProperty("user"));
            propTable.put("password", System.getProperty("password"));
        }
    }

против

public class MyClass 
{
    public MyClass()
    {
        synchronized (MyClass.class) 
        {
            if (propTable == null)
            {
                try 
                {
                    propTable.load(new FileInputStream("/data/user.prop"));
                } 
                catch (Exception e) 
                {
                    propTable.put("user", System.getProperty("user"));
                    propTable.put("password", System.getProperty("password"));
                }
            }
        }
    }

Не забывайте, теперь вы должны синхронизироваться на уровне класса, а не на уровне экземпляра. Это влечет за собой стоимость для каждого созданного экземпляра вместо единовременных затрат при загрузке класса. Плюс это некрасиво; -)

13 голосов
/ 30 января 2012

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

/**
 * Demonstrate order of initialization in Java.
 * @author Daniel S. Wilkerson
 */
public class CtorOrder {
  public static void main(String[] args) {
    B a = new B();
  }
}

class A {
  A() {
    System.out.println("A ctor");
  }
}

class B extends A {

  int x = initX();

  int initX() {
    System.out.println("B initX");
    return 1;
  }

  B() {
    super();
    System.out.println("B ctor");
  }

}

Выход:

java CtorOrder
A ctor
B initX
B ctor
7 голосов
/ 30 апреля 2009

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

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

Иногда, если у вас есть какая-то логика, которая сложна для цепочки между конструкторами (скажем, вы используете подклассы и вы не можете вызвать this (), потому что вам нужно вызвать super ()), вы можете избежать дублирования, выполняя обычные вещи в случае инициализатора. Инициализаторы экземпляров настолько редки, что для многих они являются неожиданным синтаксисом, поэтому я избегаю их и предпочел бы сделать мой класс конкретным, а не анонимным, если мне нужно поведение конструктора.

JMock - исключение, потому что именно так предполагается использовать фреймворк.

5 голосов
/ 30 мая 2018

Есть один важный аспект, который вы должны учитывать при выборе:

Блоки инициализатора являются членами класса / объекта, в то время как конструкторы не являются . Это важно при рассмотрении расширения / подкласса :

  1. Инициализаторы наследуются подклассами. (Хотя, может быть затенено)
    Это означает, что в основном гарантируется, что подклассы инициализируются, как и предполагалось родительским классом.
  2. Однако конструкторы не наследуются . (Они вызывают только super() [то есть без параметров] неявно, или вам нужно сделать определенный super(...) вызов вручную.)
    Это означает, что возможно, что неявный или неявный вызов super(...) может не инициализировать подкласс, как это предусмотрено родительским классом.

Рассмотрим пример блока инициализатора:

class ParentWithInitializer {
    protected final String aFieldToInitialize;

    {
        aFieldToInitialize = "init";
        System.out.println("initializing in initializer block of: " 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithInitializer extends ParentWithInitializer{
    public static void main(String... args){
        System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
    }
}

выход:
initializing in initializer block of: ChildOfParentWithInitializer init
-> Независимо от того, какие конструкторы реализует подкласс, поле будет инициализировано.

Теперь рассмотрим этот пример с конструкторами:

class ParentWithConstructor {
    protected final String aFieldToInitialize;

    // different constructors initialize the value differently:
    ParentWithConstructor(){
        //init a null object
        aFieldToInitialize = null;
        System.out.println("Constructor of " 
            + this.getClass().getSimpleName() + " inits to null");
    }

    ParentWithConstructor(String... params) {
        //init all fields to intended values
        aFieldToInitialize = "intended init Value";
        System.out.println("initializing in parameterized constructor of:" 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithConstructor extends ParentWithConstructor{
    public static void main (String... args){
        System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
    }
}

выход:
Constructor of ChildOfParentWithConstructor inits to null null
-> Это инициализирует поле по умолчанию null, даже если это может быть не тот результат, который вы хотели.

4 голосов
/ 18 июня 2011

Я также хотел бы добавить одно очко наряду со всеми выше сказочными ответами. Когда мы загружаем драйвер в JDBC с помощью Class.forName (""), происходит загрузка класса, и запускается статический инициализатор класса Driver, и код внутри него регистрирует драйвер в Driver Manager. Это одно из значительных применений статического кодового блока.

3 голосов
/ 30 апреля 2009

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

С другой стороны, это единственный способ сделать несколько вещей (я думаю, вы в значительной степени покрыли это).

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

...