这里发生了两件事,让我们来看看它们:
首先,您要创建两个不同的字段。看一下字节码的(非常孤立的)块,你会看到这个:
class Father {
public java.lang.String x;
// Method descriptor #17 ()V
// Stack: 2, Locals: 1
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 {
// Field descriptor #6 Ljava/lang/String;
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 {
// Field descriptor #6 Ljava/lang/String;
public java.lang.String x;
// Method descriptor #8 ()V
// Stack: 2, Locals: 1
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 行看起来不错:并且已加载,并且字段设置为 。为什么?因为 JVM 可以找到继承的字段。但重要的是要注意,即使该字段被引用为,JVM找到的字段实际上是。this
"Son"
putfield
Son.x
Son.x
Father.x
那么它是否给出了正确的输出呢?很遗憾,没有:
I'm Son
Father
原因是语句的顺序。字节码中的第 0 行和第 1 行是隐式调用,因此语句的顺序如下所示:super();
System.out.println(this);
System.out.println(this.x);
x = "Son";
当然,它将打印。为了摆脱这种情况,可以做一些事情。"Father"
可能最干净的是:不要在构造函数中打印!只要构造函数尚未完成,对象就不会完全初始化。您正在假设,由于 s 是构造函数中的最后一个语句,因此您的对象是完整的。正如您所体验的那样,当您有子类时,情况并非如此,因为超类构造函数将始终在子类有机会初始化对象之前完成。println
有些人认为这是构造函数本身概念的缺陷;有些语言甚至不在这个意义上使用构造函数。您可以改用 init()
方法。在普通方法中,您具有多态性的优势,因此您可以调用引用,并被调用;然而, 总是创建一个对象。(当然,在Java中,你仍然需要在某个时候调用正确的构造函数)。init()
Father
Son.init()
new Father()
Father
但我认为你需要的是这样的东西:
class Father {
public String x;
public Father() {
init();
System.out.println(this);//[2]It is called in Father constructor
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() {
//you could do super.init(); here in cases where it's possibly not redundant
x = "Son";
}
@Override
public String toString() {
return "I'm Son";
}
}
我没有它的名字,但试试吧。它将打印
I'm Son
Son
这到底是怎么回事呢?最上面的构造函数(的 构造函数)调用一个方法,该方法在子类中被重写。由于所有构造函数都首先调用,因此它们有效地执行超类到子类。因此,如果最顶层构造函数的第一次调用是,则所有 init 都发生在任何构造函数代码之前。如果 init 方法完全初始化对象,则所有构造函数都可以使用初始化的对象。由于它是多态的,它甚至可以在有子类时初始化对象,这与构造函数不同。Father
init()
super();
init();
init()
请注意,这是受保护的:子类将能够调用和覆盖它,但其他包中的类将无法调用它。这是一个轻微的改进,也应该考虑。init()
public
x