为什么实例字段的值变为空?
Why is the value of the instance field coming null?
我有这段简单的代码。
abstract class X {
X() {
read();
}
private void read() {
Object obj = new Object();
readValue(obj);
}
protected abstract void readValue(Object obj);
}
class Y extends X {
Object obj = null;
Y() {
super();
}
@Override
protected void readValue(Object obj) {
this.obj = obj;
}
void printer() {
System.out.println("Object = " + obj);
}
}
class Runner {
public static void main(String[] args) {
Y y = new Y();
y.printer();
}
}
当我 运行 上述代码时,对象被打印为 null。 (我得到 "Object = null")
令人惊讶的是,在 class Y 中删除空声明时
Object obj;
打印对象的实际值。
像 ("Object = java.lang.Object@3cd1a2f1")
为什么会观察到这种行为? 'this' 指向什么?如果我们只是声明任何对象,它都会被 null 初始化,那么为什么会出现这种异常行为?
obj
字段为空的原因是 Y
的构造函数调用中发生的步骤顺序:
Y
的构造函数调用最终调用具体 class Y
的 readValue
的超级构造函数,因此将非空值分配给 obj
字段.
超级构造函数完成后,实例字段obj
由于变量初始化器被初始化为null:
Object obj = null;
当您删除 null
初始值设定项时,它变成一个简单的字段声明,无需在步骤 2 中执行实例初始化。
恰当的解决方案不是删除 null
初始化程序,而是重新设计整个 class 层次结构。例如,由于 readValue
的目的似乎只是一个变量的 setter,那么你不需要让它重写父 class 中的抽象方法。只需将其设置为单独的方法,并在 Y
的构造函数完成后调用它即可。
这说明了从超类构造函数调用子类中继承的方法的危险。主要的危险是子类 运行 超类构造函数完成后 中变量的初始值设定项。
事情是这样的。
- 已创建
y
个对象。
- 超类构造函数
X()
被调用,它调用read()
.
read
方法新建一个Object
传给readValue
,在Y
. 中实现
Y
中的 readValue
方法将 obj
设置为新对象。
- 超类构造函数
X()
完成,初始化器 运行 现在 在 Y
中,将 obj
设置为 null
.
printer
方法打印 "Object = null"
.
如果在 Y
中删除 obj
的声明,则没有 运行 的初始值设定项,并且 obj
变量保留其值。
[A]ll the instance variables in the new object, including those declared in superclasses, are initialized to their default values (§4.12.5).
Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:
Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.
If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.
This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.
Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.
Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.
(强调我的)
和
Unlike C++, the Java programming language does not specify altered rules for method dispatch during the creation of a new class instance. If methods are invoked that are overridden in subclasses in the object being initialized, then these overriding methods are used, even before the new object is completely initialized.
对象为空,因为 superclass 构造函数在 subclass 构造函数之前运行,因此语句 Object obj = null;在调用超级class构造函数后执行。
赋值对象obj = null;在编译期间内联到构造函数中。这只能在现有实例中访问,
当你在构造函数中时实例还不存在(它仍在构造中)。
您可以通过将对象对象声明为静态来实现对象值(Object = java.lang.Object@3cd1a2f1)。
static 对象 obj = null;
但一般来说,从构造函数实时调用重写方法是不好的做法。
我有这段简单的代码。
abstract class X {
X() {
read();
}
private void read() {
Object obj = new Object();
readValue(obj);
}
protected abstract void readValue(Object obj);
}
class Y extends X {
Object obj = null;
Y() {
super();
}
@Override
protected void readValue(Object obj) {
this.obj = obj;
}
void printer() {
System.out.println("Object = " + obj);
}
}
class Runner {
public static void main(String[] args) {
Y y = new Y();
y.printer();
}
}
当我 运行 上述代码时,对象被打印为 null。 (我得到 "Object = null")
令人惊讶的是,在 class Y 中删除空声明时
Object obj;
打印对象的实际值。
像 ("Object = java.lang.Object@3cd1a2f1")
为什么会观察到这种行为? 'this' 指向什么?如果我们只是声明任何对象,它都会被 null 初始化,那么为什么会出现这种异常行为?
obj
字段为空的原因是 Y
的构造函数调用中发生的步骤顺序:
Y
的构造函数调用最终调用具体 classY
的readValue
的超级构造函数,因此将非空值分配给obj
字段.超级构造函数完成后,实例字段
obj
由于变量初始化器被初始化为null:Object obj = null;
当您删除 null
初始值设定项时,它变成一个简单的字段声明,无需在步骤 2 中执行实例初始化。
恰当的解决方案不是删除 null
初始化程序,而是重新设计整个 class 层次结构。例如,由于 readValue
的目的似乎只是一个变量的 setter,那么你不需要让它重写父 class 中的抽象方法。只需将其设置为单独的方法,并在 Y
的构造函数完成后调用它即可。
这说明了从超类构造函数调用子类中继承的方法的危险。主要的危险是子类 运行 超类构造函数完成后 中变量的初始值设定项。
事情是这样的。
- 已创建
y
个对象。 - 超类构造函数
X()
被调用,它调用read()
. read
方法新建一个Object
传给readValue
,在Y
. 中实现
Y
中的readValue
方法将obj
设置为新对象。- 超类构造函数
X()
完成,初始化器 运行 现在 在Y
中,将obj
设置为null
. printer
方法打印"Object = null"
.
如果在 Y
中删除 obj
的声明,则没有 运行 的初始值设定项,并且 obj
变量保留其值。
[A]ll the instance variables in the new object, including those declared in superclasses, are initialized to their default values (§4.12.5).
Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:
Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.
If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.
This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.
Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.
Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.
(强调我的)
和
Unlike C++, the Java programming language does not specify altered rules for method dispatch during the creation of a new class instance. If methods are invoked that are overridden in subclasses in the object being initialized, then these overriding methods are used, even before the new object is completely initialized.
对象为空,因为 superclass 构造函数在 subclass 构造函数之前运行,因此语句 Object obj = null;在调用超级class构造函数后执行。
赋值对象obj = null;在编译期间内联到构造函数中。这只能在现有实例中访问, 当你在构造函数中时实例还不存在(它仍在构造中)。
您可以通过将对象对象声明为静态来实现对象值(Object = java.lang.Object@3cd1a2f1)。
static 对象 obj = null;
但一般来说,从构造函数实时调用重写方法是不好的做法。