这里发生了两件事情,我们来看一下:
首先,您正在创建两个不同的字段。查看(非常隔离的)字节码块,可以看到以下内容:
class Father {
public java.lang.String x;
public Father();
...
10 getstatic java.lang.System.out : java.io.PrintStream [23]
13 aload_0 [this]
14 invokevirtual java.io.PrintStream.println(java.lang.Object) : void [29]
17 getstatic java.lang.System.out : java.io.PrintStream [23]
20 aload_0 [this]
21 getfield Father.x : java.lang.String [21]
24 invokevirtual java.io.PrintStream.println(java.lang.String) : void [35]
27 return
}
class Son extends Father {
public java.lang.String x;
}
重要的是第13、20和21行;其它代表System.out.println();
本身,或者是隐式的return;
。 aload_0
加载this
引用,getfield
从对象中检索字段值,在这种情况下是从this
中检索。您在此处看到的是字段名称被限定为Father.x
。在Son
中的一行中,您可以看到有一个单独的字段。但是Son.x
从未被使用,只有Father.x
。
现在,如果我们删除Son.x
并添加以下构造函数会怎样:
public Son() {
x = "Son";
}
首先看字节码:
class Son extends Father {
public java.lang.String x;
Son();
0 aload_0 [this]
1 invokespecial Father() [10]
4 aload_0 [this]
5 ldc <String "Son"> [12]
7 putfield Son.x : java.lang.String [13]
10 return
}
第4、5和7行都是正确的:this
和"Son"
已被加载,并使用putfield
设置了字段。为什么要使用Son.x
?因为JVM可以找到继承的字段。但需要注意的是,即使该字段被引用为Son.x
,JVM找到的实际上是Father.x
。
那么它是否输出了正确的结果呢?不幸的是,没有:
I'm Son
Father
问题在于语句的顺序。在字节码中,第0行和第1行是隐式的super();
调用,因此语句的顺序如下:
答案:问题在于语句的顺序。在字节码中,第0行和第1行是隐式的super();
调用,因此语句的顺序如下:
System.out.println(this)
System.out.println(this.x)
x = "Son"
当然会打印"Father"
。为了消除这个问题,可以采取一些措施。
可能最干净的方法是:不要在构造函数中打印!只要构造函数没有完成,对象就没有完全初始化。您假设由于println
是构造函数中的最后一个语句,因此对象已经完成。但是您可能会遇到子类的情况,因为在子类初始化对象之前,超类构造函数将始终完成。
有些人认为这是构造函数本身的缺陷;有些语言甚至不使用构造函数。您可以使用init()
方法。在普通方法中,您具有多态的优势,因此可以在Father
引用上调用init()
,从而调用Son.init()
;而new Father()
始终创建Father
对象。 (当然,在Java中,您仍然需要在某个时候调用正确的构造函数)。
但我认为您需要的是像这样的东西:
class Father {
public String x;
public Father() {
init();
System.out.println(this);
System.out.println(this.x);
}
protected void init() {
x = "Father";
}
@Override
public String toString() {
return "I'm Father";
}
}
class Son extends Father {
@Override
protected void init() {
x = "Son";
}
@Override
public String toString() {
return "I'm Son";
}
}
我没有给它取名,但是试一下。它会打印出来。
I'm Son
Son
那么这里到底发生了什么?你最上面的构造函数(即Father
)调用了一个init()
方法,这个方法在子类中被覆盖。由于所有构造器都会先调用super();
,它们实际上是按照从超类到子类的顺序执行的。因此,如果最上面的构造函数的第一个调用是init();
,那么所有的初始化都将在任何构造函数代码之前发生。如果你的init()
方法完全初始化了对象,那么所有的构造函数都可以使用已初始化的对象。并且由于init()
是多态的,即使有子类存在,它也可以对对象进行初始化,而构造器就不行。
请注意,init()
是受保护的:子类将能够调用和覆盖它,但其他包中的类将无法调用它。这比public
稍微好一点,应该也考虑用于x
。