Использование инициализаторов и конструкторов в Java

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

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

Я предполагаю, что вы говорите оба:

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

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

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

Ответ 2

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

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

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

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