为什么实例字段的值为空?
我有这段简单的代码。
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” )
令人惊讶的是,在Y类中删除null声明时
Object obj;
打印对象的实际值。
像( “Object = java.lang.Object@3cd1a2f1” )
为什么会出现这种行为? 什么是’这个’指向? 如果我们只是声明它,那么任何对象都被null初始化,那么为什么会出现这种exception行为呢?
这说明了从超类构造函数中调用子类中的inheritance方法的危险。 主要的危险是子类中变量的初始化器在超类构造函数完成后运行。
这是发生了什么。
- 创建
y
的对象。 - 调用超类构造函数
X()
,它调用read()
。 -
read
方法创建一个新的Object
并将其传递给readValue
,它在Y
实现。 -
Y
的readValue
方法将obj
设置为新对象。 - 超类构造函数
X()
完成,初始化程序现在在Y
运行,将obj
设置为null
。 -
printer
方法打印"Object = null"
。
如果在Y
删除了obj
的声明,则没有初始化程序可以运行,并且obj
变量保留其值。
JLS第12.5节规定:
[A]将新对象中的实例变量(包括在超类中声明的变量)初始化为其默认值(§4.12.5)。
在作为结果返回对新创建的对象的引用之前,处理指示的构造函数以使用以下过程初始化新对象:
将构造函数的参数分配给此构造函数调用的新创建的参数变量。
如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7.1节)开头(使用此),则使用这五个相同的步骤计算参数并以递归方式处理该构造函数调用。 如果该构造函数调用突然完成,则此过程突然完成,原因相同; 否则,继续步骤5。
此构造函数不是以同一个类中的另一个构造函数的显式构造函数调用开始的(使用此方法)。 如果此构造函数用于Object以外的类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。 使用这五个相同的步骤评估参数并递归处理超类构造函数调用。 如果该构造函数调用突然完成,则由于同样的原因,此过程突然完成。 否则,继续执行步骤4。
为此类执行实例初始值设定项和实例变量初始值设定项 ,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,在这些顺序中,它们以文本方式显示在类的源代码中。 如果执行任何这些初始值设定项导致exception,则不会处理其他初始值设定项,并且此过程会突然完成同样的exception。 否则,继续步骤5。
执行此构造函数的其余部分。 如果该执行突然完成,则由于同样的原因,此过程突然完成。 否则,此过程正常完成。
(强调我的)
和
与C ++不同,Java编程语言在创建新类实例期间未指定方法分派的更改规则。 如果调用在正在初始化的对象的子类中重写的方法,则使用这些重写方法,即使在新对象完全初始化之前也是如此。
obj
字段为null的原因是由于Y
的构造函数调用中发生的步骤序列:
-
Y
的构造函数调用超级构造函数,该构造函数最终调用具体类Y
readValue
,因此为obj
字段分配非null值。 -
超级构造函数完成后,由于变量初始化程序,实例字段
obj
初始化为null:Object obj = null;
删除null
初始化程序时,它将成为一个简单的字段声明,在步骤2中不执行实例初始化。
apt解决方案不是删除null
初始化程序,而是重新设计整个类层次结构。 例如,由于readValue
的目的似乎只是变量的setter,因此您不需要使它覆盖父类中的抽象方法。 只需将其设置为单独的方法,并在Y
的构造函数完成后调用它。
该对象为null,因为超类构造函数在子类构造函数之前运行,因此语句Object obj = null是很自然的。 在调用超类构造函数后执行。
Object obj = null的赋值; 在编译期间内联到构造函数中。 这只能在现有实例中访问,并且当您在构造函数中时,实例仍然不存在(它仍在构建中)。
您可以通过将对象声明为静态来实现对象值(Object = java.lang.Object@3cd1a2f1)。
static Object obj = null;
但一般来说,从构造函数实时调用overriden方法是不好的做法。