Переменная экземпляра и конструктор
Я задал несколько вопросов по этому вопросу, но, похоже, каждый раз, когда я получаю ответ, у меня появляется больше вопросов. Этот вопрос является продолжением другого моего вопроса: инициализация в полиморфизме переменных
В любом случае рассмотрим следующий пример.
class A{ //1
int a = 1; //2
}
я слышал это концептуально выглядит,
class A { //1
int a = 0; //2
A() { //3
super(); //4
a = 1; //5
}
Из того, что я понимаю, это потому, что каждый раз, когда создается объект, объекты экземпляра инициализируются в значения по умолчанию.
Если я поставлю блок инициализации, скажем,
System.out.print(i);
справа под строкой 2 для обоих примеров top напечатает 1, а bottom напечатает 0. Насколько я знаю, блок инициализации выполняется перед конструктором. Так это только концептуальное представление только конструкторов? Или код действительно изменяется как таковой, когда вызывается конструктор по умолчанию? Может кто-нибудь уточнить это для меня?
Почему так себя ведет? В моем другом вопросе это, казалось, только вызывало путаницу в отношении того, какая переменная вызывается. Разве переменная экземпляра не может быть просто объявлена =1 и используется во всем классе? Разве это не должно сделать это проще?
4 ответа
Разница между вашим примером заключается в порядке операций. В вашем первом примере с блоком инициализатора, где вы сказали, порядок:
- Назначить 1 для
a
(в декларации) - Выход
a
(в блоке инициализатора)
... но в вашем примере это
- Назначьте 0 (значение по умолчанию)
a
(эффективно в декларации) - Выход
a
(в блоке инициализации) - Назначить 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 будет
- сначала создать экземпляр переменной по умолчанию, если это не сделано пользователем,
- затем он будет проходить через любой блок инициализатора экземпляра в порядке их появления, и, наконец,
- это войдет в конструктор.