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

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

public class WhyIsThisOk {
    { a = 5; } // why is this ok???
    int a = 10;

    public WhyIsThisOk() {
    }

    public static void main(String[] args) {
        WhyIsThisOk why = new WhyIsThisOk();
        System.out.println(why.a); // 10
    }
}

5 ответов

Решение

Из документов:

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

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

public class WrongVersionOfWhyIsThisOk {

    int a = 10;

    public WhyIsThisOk (){
        a = 5;
    }

    public static void main(String[] args){
        WrongVersionOfWhyIsThisOk why = new WrongVersionOfWhyIsThisOk ();
        System.out.println(why.a);
    }
}

Но работает WrongVersionOfWhyIsThisOk будет выдавать 5 вместо 10 того оригинального кода.

Но на самом деле это и блок инициализатора, и присвоение переменной, копируемой в конструктор:

public class RightVersionOfWhyIsThisOk {

    int a;

    public RightVersionOfWhyIsThisOk (){
        a = 5;
        a = 10;
    }

    public static void main(String[] args){
        RightVersionOfWhyIsThisOk why = new RightVersionOfWhyIsThisOk ();
        System.out.println(why.a);
    }
}

Обновить:

Вот документ, подробно описывающий порядок инициализации и вызов конструктора:

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

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

Из документов:

8.3.2.3. Ограничения на использование полей при инициализации

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

  • Использование происходит в экземпляре (соответственно статической) переменной инициализатора C или в экземпляре (соответственно статической) инициализатора
    из C.

  • Использование не на левой стороне назначения.

  • Использование через простое имя.

  • C является самым внутренним классом или интерфейсом, включающим использование.

Это ошибка времени компиляции, если какое-либо из четырех требований выше не выполняется

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

Блоки инициализации экземпляра вызываются во время создания экземпляра. Поэтому вполне нормально, что после создания объекта Why он работает.

Порядок инициализации: 1) статический блок 2) конструктор 3) экземпляры блоков в порядке появления

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

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

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

public  class WhyIsThisOk {

    {
        a = 5;
    }

    public WhyIsThisOk() {
    }

    public static void main(String[] args) {
        System.out.println(new WhyIsThisOk().a);
    }

    int a = 10;
}

Важно то, что компилятор сначала копирует (сверху вниз) a=5 а потом a=10 в конструктор, так это выглядит так:

public WhyIsThisOk() {
    a = 5;
    a = 10;
}

Наконец, посмотрите на этот пример:

public class WhyIsThisOk {

    {
        a = get5();
    }

    public WhyIsThisOk() {
        a = get7();
    }

    public static void main(String[] args) {
        System.out.println(new WhyIsThisOk().a);
    }

    int a = get10();

    public static int get5() {
        System.out.println("get5 from: " + new Exception().getStackTrace()[1].getMethodName());
        return 5;
    }

    public static int get7() {
        System.out.println("get7 from: " + new Exception().getStackTrace()[1].getMethodName());
        return 7;
    }

    public static int get10() {
        System.out.println("get10 from: " + new Exception().getStackTrace()[1].getMethodName());
        return 10;
    }
}

Выход:

get5 from: <init>
get10 from: <init>
get7 from: <init>
7
Другие вопросы по тегам