Подтвердить что ты не робот

Инициализатор экземпляра и * это * ключевое слово

Попытка скомпилировать этот фрагмент кода

public class Main {

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

    { System.out.println(x); } //Error here

    int x=1;
}

создает ошибку cannot reference a field before it is defined. Но если я изменил строку инициализации на

    { System.out.println(this.x); }

он работает как прелесть, печатает значение int по умолчанию 0.

Это немного сбивает меня с толку, почему this имеет значение? Разве это не должно быть избыточным в этом случае? Может ли кто-нибудь объяснить мне, что происходит за кулисами, чтобы понять, как это работает?

PS: Я знаю, что объявив x, прежде чем инициализатор заставит его работать.

4b9b3361

Ответ 1

Я попробую объяснить его на уровне компилятора.

Скажем, у вас есть такой метод, как:

int x;
x = 1;
System.out.println(x);

Будет выполнена компиляция и выполнение. Если вы измените метод на это:

System.out.println(x);
int x;
x = 1;

Он даже не будет компилировать то же самое с вашим примером.

Компилятор копирует код { } intializer в ctor, а также инициализацию x=1.

Как вы сказали, это работает, если вы установите x=1 перед { } intializer.

public class MainC {

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

    int x=1;
    {
      System.out.println(x);
    }
}

См. следующий байт-код Java:

  public MainC();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field x:I
         9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        12: aload_0
        13: getfield      #2                  // Field x:I
        16: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        19: return
      LineNumberTable:
        line 1: 0
        line 7: 4
        line 9: 9
        line 10: 19

Поле x объявлено и получает значение 1, прежде чем оно будет использоваться в System.out.println вызов.

Итак, почему это не работает, если вы установите его после { } по той же причине вы не можете использовать Кодекс моего второго примера. Поле объявляется после использования, что не имеет смысла.

Итак, почему он работает с ключевым словом this?!

Давайте посмотрим на какой-то код:

public class Main {

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

    { System.out.println(this.x); } //Error here

    int x=1;
}

Соответствующий Java-байт-код для ctor:

  public Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: aload_0
         8: getfield      #3                  // Field x:I
        11: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        14: aload_0
        15: iconst_1
        16: putfield      #3                  // Field x:I
        19: return
      LineNumberTable:
        line 1: 0
        line 7: 4
        line 9: 14

Так что происходит здесь? Легко говоря, ключевое слово this загружает Основной объект ссылку на стек. После этого к полю x можно получить доступ, поэтому вызов System.out.println может быть успешно выполнен.

Ответ 2

JSL 8.6 должен объяснить вашу ошибку времени компиляции:

Инициализаторы экземпляров разрешают ссылаться на текущий объект через ключевое слово this (§15.8.3)...

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

В §8.3.3 говорится:

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

  • Объявление переменной экземпляра в классе или интерфейсе C появляется после использования переменной экземпляра text

  • Использование - это простое имя в инициализаторе переменной экземпляра C или инициализаторе экземпляра C;

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

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

Вот почему запись простого имени x дает вам ошибку.

Ответ 3

Как в JSL (§15.8.3)

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

this ключевое слово, будучи вызванным, создает экземпляр класса в фоновом режиме. Когда вы вызываете { System.out.println(this.x); }, переменная i создается с классом Main.

Ответ 4

Инициализаторы экземпляров могут ссылаться на текущий объект через ключевое слово this (§15.8.3), чтобы использовать ключевое слово super (§15.11.2, §15.12) и использовать любые переменные типа в области.

Инициализация состоит из выполнения любой переменной класса инициализаторы и статические инициализаторы класса Test, в текстовом порядок.

Что происходит, так это то, что jvm выделяет память для x в фрейме стека. Когда вы используете this.x 0, возвращается, поскольку переменная уже выделена. Вы получите 1, если переменная статична по мере ее инициализации.