我有这段简单的代码。
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,那么为什么会有这种异常行为呢?
这说明了从超类构造函数的子类中调用继承方法的危险。主要危险在于,超类构造函数完成 后 ,子类中变量的初始化器就会运行。
这是发生了什么。
y
X()
read()
read
Object
readValue
Y
obj
null
printer
"Object = null"
如果删除objin 中的声明Y,则没有初始化程序可运行,并且obj变量保留其值。
在JLS,12.5节,状态:
将新对象中的实例变量(包括在超类中声明的实例变量)初始化为其默认值(第4.12.5节)。 在返回对新创建对象的引用作为结果之前,使用以下过程处理指示的构造函数以初始化新对象: 将构造函数的参数分配给此构造函数调用的新创建的参数变量。 2. 如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7节)开始(使用此方法),则使用相同的五个步骤评估参数并递归处理该构造函数调用。如果该构造函数调用突然完成,则该过程由于相同的原因而突然完成;否则,请继续执行步骤5。 3. 此构造函数并不以对同一个类中的另一个构造函数的显式构造函数调用(使用此函数)开头。如果此构造函数用于Object以外的其他类,则此构造函数将以显式或隐式调用超类构造函数(使用super)开始。使用这五个相同的步骤来递归评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则出于相同原因,此过程也会突然完成。否则,请继续执行步骤4。 执行该类 的实例初始值设定项 和实例变量初始 值设定项,并按从左到右的顺序将实例变量初始值设定项的值分配给相应的实例变量,这些变量在文本中显示在该类的源代码中。如果执行这些初始化程序中的任何一个导致异常,则不会再处理其他初始化程序,并且该过程会因相同的异常而突然完成。否则,请继续执行步骤5。 执行此构造函数的其余部分。如果该执行突然完成,则出于相同原因,此过程也会突然完成。否则,此过程将正常完成。
将新对象中的实例变量(包括在超类中声明的实例变量)初始化为其默认值(第4.12.5节)。
在返回对新创建对象的引用作为结果之前,使用以下过程处理指示的构造函数以初始化新对象:
2. 如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7节)开始(使用此方法),则使用相同的五个步骤评估参数并递归处理该构造函数调用。如果该构造函数调用突然完成,则该过程由于相同的原因而突然完成;否则,请继续执行步骤5。
3. 此构造函数并不以对同一个类中的另一个构造函数的显式构造函数调用(使用此函数)开头。如果此构造函数用于Object以外的其他类,则此构造函数将以显式或隐式调用超类构造函数(使用super)开始。使用这五个相同的步骤来递归评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则出于相同原因,此过程也会突然完成。否则,请继续执行步骤4。
执行该类 的实例初始值设定项 和实例变量初始 值设定项,并按从左到右的顺序将实例变量初始值设定项的值分配给相应的实例变量,这些变量在文本中显示在该类的源代码中。如果执行这些初始化程序中的任何一个导致异常,则不会再处理其他初始化程序,并且该过程会因相同的异常而突然完成。否则,请继续执行步骤5。
执行此构造函数的其余部分。如果该执行突然完成,则出于相同原因,此过程也会突然完成。否则,此过程将正常完成。
(强调我的)
和
与C ++不同,Java编程语言没有在创建新的类实例的过程中为方法分派指定更改的规则。如果调用的方法在要初始化的对象的子类中被覆盖,则即使在完全初始化新对象之前,也会使用这些覆盖方法。