В чем разница между статическим и нестатическим блоком кода инициализации

357

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

public class Test {
    private static final int a;    
    static {
        a = 5;
        doSomething(a);
    }
    private static int doSomething(int x) {
        return (x+5);
    }
}

Если удалить staticключевое слово он жалуется , потому что переменная aесть final. Однако можно удалить finalи staticключевые слова, и сделать их компиляцией.

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

Сере Дери
источник

Ответы:

403

Блок кода с модификатором static означает инициализатор класса ; без статического модификатора блок кода является экземпляром инициализатором .

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

Инициализаторы экземпляра выполняются в порядке, определенном при создании экземпляра класса, непосредственно перед выполнением кода конструктора, сразу после вызова супер-конструктора.

Если удалить staticиз int a, становится переменным экземпляром, который вы не в состоянии получить доступ из статической инициализации блока. Это не скомпилируется с ошибкой «на нестатическую переменную a нельзя ссылаться из статического контекста».

Если вы также удалите его staticиз блока инициализатора, он станет инициализатором экземпляра и int aинициализируется при создании.

Лоуренс Дол
источник
Статический инициализатор фактически вызывается позже, когда класс инициализируется, после того, как он был загружен и связан. Это происходит, когда вы создаете экземпляр объекта класса или обращаетесь к статической переменной или методу класса. На самом деле, если у вас есть класс со статическим инициализатором и методом public static void staticMethod(){}, если вы выполняете TestStatic.class.getMethod("staticMethod");. Статический инициализатор не будет вызван. Более подробная информация здесь docs.oracle.com/javase/specs/jvms/se10/html/…
Тото,
@ Totò: Да, именно это влечет за собой разрешение класса (по крайней мере, они называли его ссылкой + init как «разрешением», когда). Я не удивлен, что вы можете использовать рефлексию, чтобы узнать что-то о классе без его разрешения.
Лоуренс Дол
166

Уфф! что такое статический инициализатор?

Статический инициализатор - это static {}блок кода внутри java-класса, который запускается только один раз перед вызовом конструктора или метода main.

ХОРОШО! Расскажи больше ...

  • это блок кода static { ... }внутри любого Java-класса. и выполняется виртуальной машиной при вызове класса.
  • Никакие returnзаявления не поддерживаются.
  • Аргументы не поддерживаются.
  • Нет thisили superподдерживаются.

Хм, где я могу использовать это?

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

Не просто лаять! где пример?

package com.example.learnjava;

import java.util.ArrayList;

public class Fruit {

    static {
        System.out.println("Inside Static Initializer.");

        // fruits array
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Pear");

        // print fruits
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        System.out.println("End Static Initializer.\n");
    }

    public static void main(String[] args) {
        System.out.println("Inside Main Method.");
    }
}

Вывод???

Внутри статический инициализатор.

яблоко

оранжевый

Груша

Завершить статический инициализатор.

Внутри основной метод.

Надеюсь это поможет!

Мадан Сапкота
источник
Спасибо, Мадан! Может статический блок будет использоваться вместо afterPropertiesSet()из InitializingBean?
Александр Сурафел
3
Да, ты можешь! Статический инициализатор вызывается, когда класс загружается jvm. Так что это действительно первый этап, на котором выполняется код. Если у вас также есть конструктор, порядок будет таким: статический инициализатор, конструктор, afterPropertiesSet
Мартин Баумгартнер,
57

staticБлок является «статическим инициализатором».

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

Я лично использовал его только когда писал код JNI:

class JNIGlue {
    static {
        System.loadLibrary("foo");
    }
}
Альнитак
источник
6
Нет, нет явного способа вызвать его, инициализатор класса никогда не представляется Methodэкземпляром, а вызывается только виртуальной машиной Java.
Рафаэль Винтерхальтер
46

Это прямо с http://www.programcreek.com/2011/10/java-class-instance-initializers/

1. Выполнение заказа

Посмотрите на следующий класс, знаете ли вы, какой из них выполняется первым?

public class Foo {

    //instance variable initializer
    String s = "abc";

    //constructor
    public Foo() {
        System.out.println("constructor called");
    }

    //static initializer
    static {
        System.out.println("static initializer called");
    }

    //instance initializer
    {
        System.out.println("instance initializer called");
    }

    public static void main(String[] args) {
        new Foo();
        new Foo();
    }
}

Вывод:

статический инициализатор называется

инициализатор экземпляра называется

конструктор называется

инициализатор экземпляра называется

конструктор называется

2. Как работает инициализатор экземпляра Java?

Приведенный выше инициализатор экземпляра содержит оператор println. Чтобы понять , как это работает, мы можем рассматривать его как оператор присваивания переменной, например, b = 0. Это может сделать это более очевидным для понимания.

Вместо

int b = 0Вы могли бы написать

int b;
b = 0;

Следовательно, инициализаторы экземпляров и инициализаторы переменных экземпляров практически одинаковы.

3. Когда инициализаторы экземпляра полезны?

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

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

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

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

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

Благодаря Derhein.

Также обратите внимание, что у анонимных классов, которые реализуют интерфейсы [1], нет конструкторов. Поэтому инициализаторы экземпляров необходимы для выполнения любых видов выражений во время построения.

Алексей Фандо
источник
12

«final» гарантирует, что переменная должна быть инициализирована до завершения кода инициализатора объекта. Аналогично, «static final» гарантирует, что переменная будет инициализирована в конце кода инициализации класса. Отсутствие «статического» кода инициализации превращает его в код инициализации объекта; таким образом, ваша переменная больше не удовлетворяет своим гарантиям.

DJClayworth
источник
8

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

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

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

Винсент Рамдани
источник
6

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

Пример:

следующий код:

class MyClass {

    private int myField = 3;
    {
        myField = myField + 2;
        //myField is worth 5 for all instance
    }

    public MyClass() {
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

эквивалентно:

class MyClass {

    private int myField = 3;

    public MyClass() {
        myField = myField + 2;
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        myField = myField + 2;
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

Надеюсь, мой пример понятен разработчикам.

Cardman
источник
4

Блок статического кода может использоваться для создания или инициализации переменных класса (в отличие от переменных объекта). Таким образом, объявление статического «a» означает, что только один объект является общим для всех объектов Test, и блок статического кода инициализирует «a» только один раз, когда класс Test загружается впервые, независимо от того, сколько объектов Test создано.

Пол Томблин
источник
В качестве продолжения, если я не создаю экземпляр объекта, а вместо этого вызываю публичную статическую функцию. Означает ли это, что этот блок гарантированно выполняется перед этим вызовом публичной функции? Спасибо.
Сере Дери
Если вы вызываете общедоступную статическую функцию класса, тогда сначала нужно загрузить класс, поэтому да, статический инициализатор будет выполняться первым.
Пол Томблин
Если только это не была инициализация класса, которая (косвенно) вызывала код, который пытается его использовать. IFYSWIM. Круговые зависимости и все такое.
Том Хотин - tackline
1
@ Том прав - можно написать что-то, где один статический инициализатор вызывает статический метод до вызова другого статического инициализатора, но мой разум отшатывается от этой мысли, поэтому я никогда не думал об этом.
Пол Томблин