Вопрос

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

статические инициализаторы экземпляра могут использоваться для установки значения "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;
    }
}

Другие советы

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

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

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")
  );

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

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

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

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

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

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

/**
 * 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

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

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

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

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

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

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

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

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

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 по умолчанию, даже если это может быть не тот результат, который вы хотели.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top