Переменная экземпляра и конструктор

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

В любом случае рассмотрим следующий пример.

class A{       //1
   int a = 1;  //2
}              

я слышал это концептуально выглядит,

class A {          //1
    int a = 0;     //2

    A() {          //3
      super();     //4
      a = 1;       //5
    }

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

  1. Если я поставлю блок инициализации, скажем,

    System.out.print(i); 
    

    справа под строкой 2 для обоих примеров top напечатает 1, а bottom напечатает 0. Насколько я знаю, блок инициализации выполняется перед конструктором. Так это только концептуальное представление только конструкторов? Или код действительно изменяется как таковой, когда вызывается конструктор по умолчанию? Может кто-нибудь уточнить это для меня?

  2. Почему так себя ведет? В моем другом вопросе это, казалось, только вызывало путаницу в отношении того, какая переменная вызывается. Разве переменная экземпляра не может быть просто объявлена ​​=1 и используется во всем классе? Разве это не должно сделать это проще?

4 ответа

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

  1. Назначить 1 для a (в декларации)
  2. Выход a (в блоке инициализатора)

... но в вашем примере это

  1. Назначьте 0 (значение по умолчанию) a (эффективно в декларации)
  2. Выход a (в блоке инициализации)
  3. Назначить 1 для a (в конструкторе)

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

Вот более полный пример. Рассмотрим этот класс:

class Example {
    // Instance field with initializer
    private int i = 5;

    // Instance initialization block
    {
        System.out.println(this.i);
    }

    // constructor 1
    Example() {
        System.out.println(this.i * 2);
    }

    // constructor 2
    Example(int _i) {
        this.i = _i;
        System.out.println(this.i * 3);
    }
}

Это скомпилировано в байт-код точно так, как если бы это было так:

class Example {
    // Instance field
    private int i;

    // constructor 1
    Example() {
        // begin copied code
        this.i = 5;
        System.out.println(this.i);
        // end copied code
        System.out.println(i * 2);
    }

    // constructor 2
    Example(int _i) {
        // begin copied code
        this.i = 5;
        System.out.println(this.i);
        // end copied code
        this.i = _i;
        System.out.println(this.i * 3);
    }
}

В обоих вышеупомянутых случаях Oracle Java 8 выводит один и тот же байт-код (как показано с помощью javap -c Example после компиляции):

Скомпилировано из "Example.java"
Пример класса {
  Пример();
    Код:
       0: aload_0
       1: invokespecial #1                  // Метод java/lang/Object."":()V
       4: aload_0
       5: iconst_5
       6: поле № 2 // Поле I:I
       9: getstatiC#3                  // Поле java/lang/System.out:Ljava/io/PrintStream;
      12: aload_0
      13: getfield      #2                  // Поле i:I
      16: invokevirtual #4                  // Метод java/io/PrintStream.println:(I)V
      19: getstatiC#3                  // Поле java/lang/System.out:Ljava/io/PrintStream;
      22: aload_0
      23: getfield      #2                  // Поле i:I
      26: iconst_2
      27: imul
      28: invokevirtual #4                  // Метод java/io/PrintStream.println:(I)V
      31: возвращение

  Пример (INT);
    Код:
       0: aload_0
       1: invokespecial #1                  // Метод java/lang/Object."":()V
       4: aload_0
       5: iconst_5
       6: поле № 2 // Поле I:I
       9: getstatiC#3                  // Поле java/lang/System.out:Ljava/io/PrintStream;
      12: aload_0
      13: getfield      #2                  // Поле i:I
      16: invokevirtual #4                  // Метод java/io/PrintStream.println:(I)V
      19: aload_0
      20: iload_1
      21: поле № 2 // Поле I: I
      24: getstatiC#3                  // Поле java/lang/System.out:Ljava/io/PrintStream;
      27: aload_0
      28: getfield      #2                  // Поле i:I
      31: iconst_3
      32: imul
      33: invokevirtual #4                  // Метод java/io/PrintStream.println:(I)V
      36: возвращение
}

Как вы сказали, эквивалентность между двумя классами в вашем вопросе только концептуальная.

Фактически, если нестатическое поле данных имеет значение инициализации, оно инициализируется перед вызовом конструктора. Блок инициализации копируется компилятором в начало каждого конструктора (после super line), поэтому он выполняется после инициализации поля и перед самим кодом конструктора.

Ваше описание того, как int a = 1 преобразование в конструктор является правильным, но это не вся история.

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

Например, если у вас есть

class A {
    {
        System.out.println(a);
    }
    int a = 1;
    {
        System.out.println(a);
        System.out.println(b);
    }
    int b = 2;
    {
        System.out.println(b);
    }
    public A() {
        // Code of A
    }
}

затем кодовый блок до Code of A выглядит так:

System.out.println(a);
a = 1;
System.out.println(a);
System.out.println(b);
b = 2;
System.out.println(b);
// Code of A

Теперь должно быть понятно, почему ноль печатается в блоке инициализации до int a = 1 в блоке, предшествующем инициализатору: блоки инициализации не обрабатываются отдельно от инициализаторов полей, их код смешивается в том же порядке, в котором они появляются в исходном коде.

Переменные экземпляра сразу же доступны с переменной по умолчанию, если не указано иное: для объектов заданы нулевые, а для примитивных типов - 0, ложные и т. Д.

У вас есть 3 варианта установки значения переменной экземпляра в Java:

1) Объявить и создать экземпляр немедленно

class A {
    int i = 1;
}

2) Создайте экземпляр в блоке инициализатора экземпляра.

class A {
    int a; // it is default value 0 at this point
    { a = 1; } //instance initializer block
} 

3) Инициализировать его в конструкторе

class A{
    int a; // it is default value 0 at this point
    A() {
        a = 1;
    }
}    

Во время создания объекта A Java будет

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