在下面的例子中,为什么String b打印null,而String c打印“gg”。
如果我错了,每当子类(BClass)覆盖超类(AClass)的受保护方法(即initClass())时,请纠正我。如果您实例化子类。超类必须使用子类指定的override方法。
public class Example {
public class AClass {
private String a;
public AClass() {
initClass();
}
protected void initClass() {
a = "randomtext";
}
}
public class BClass extends AClass {
private String b = null;
private String c;
@Override
protected void initClass() {
b = "omg!";
c = "gg";
}
public void bValue() {
System.out.println(b); // prints null
System.out.println(c); // prints "gg"
}
}
public static void main(String[] args) {
Example.BClass b = new Example().new BClass();
b.bValue();
}
}
答案 0 :(得分:3)
截至JSF 12.5
在示例中,您可以看到执行顺序。第一步是将构造函数调用到Object
构造函数。
之后会发生这种情况:
接下来,执行类[...]的实例变量的所有初始值设定项。
由于您的实例变量b初始化为null,因此之后将再次为null
答案 1 :(得分:2)
这是因为在初始化ClassB
的字段之前调用了超类构造函数。因此调用initClass()
方法设置b = "omg!"
,但是当超类构造函数返回时,b
被初始化为ClassB
中声明的值null
}。
要进行调试,设置一个断点并逐步进行,您会发现b
首先设置为null
,然后更改为omg!
,然后返回{{ 1}}。
答案 2 :(得分:2)
已经有几个关于发生了什么的正确答案。我只是想补充一点,从构造函数调用重写方法通常是不好的做法(当然,除非你确切知道你在做什么)。正如您所看到的,子类在调用其实例方法时可能尚未完全初始化(子类构造函数逻辑尚未执行,因此在未构造的危险对象上调用有效覆盖的方法)可能会导致混淆这个问题中描述的那个。
在构造函数中编写初始化逻辑要好得多,如果它太长则将其分配给从构造函数调用的几个私有方法。
答案 3 :(得分:0)
我相信this example解释了这个问题:
public class Main {
private static class PrintOnCreate {
public PrintOnCreate(String message) {
System.out.println(message);
}
}
private static class BaseClass {
private PrintOnCreate member =
new PrintOnCreate("BaseClass: member initialization");
static {
System.out.println("BaseClass: static initialization");
}
public BaseClass() {
System.out.println("BaseClass: constructor");
memberCalledFromConstructor();
}
public void memberCalledFromConstructor() {
System.out.println("BaseClass: member called from constructor");
}
}
private static class DerivedClass extends BaseClass {
private PrintOnCreate member =
new PrintOnCreate("DerivedClass: member initialization");
static {
System.out.println("DerivedClass: static initialization");
}
public DerivedClass() {
System.out.println("DerivedClass: constructor");
}
@Override
public void memberCalledFromConstructor() {
System.out.println("DerivedClass: member called from constructor");
}
}
public static void main (String[] args) {
BaseClass obj = new DerivedClass();
}
}
该程序的输出是:
BaseClass: static initialization
DerivedClass: static initialization
BaseClass: member initialization
BaseClass: constructor
DerivedClass: member called from constructor
DerivedClass: member initialization
DerivedClass: constructor
...这表明派生类的成员在基类的构造函数之后被初始化(并且派生类的成员函数的调用已经完成)。这也证明了从构造函数调用可覆盖函数的关键危险,即可以在初始化它所依赖的类的成员之前调用该函数。出于这个原因,构造函数通常应该避免调用成员函数(当它们执行时,这些函数应该是final
或static
,这样它们或者只依赖于已经初始化的当前类或者没有任何实例变量。)
答案 4 :(得分:0)
这种情况正是这样发生的,因为,首先是AClass的构造函数,它设置了b = omg!
和c=gg
的值。之后当BClass在内存中加载时它设置b=null
并且c保持原样gg,这是因为,因为在BClass中,对于b,你正在进行声明和初始化,对于c你正在做只有声明,因为c已经在内存中它甚至不会获得它的默认值,并且由于你没有对c进行任何初始化,它仍然保留在它之前的状态。