覆盖超类的受保护方法
overriding protected method of Superclass
在下面的示例中,为什么字符串 b 打印 null 而字符串 c 打印 "gg"。
如果我错了,请纠正我,只要子类 (BClass) 覆盖超类 (AClass) 的受保护方法(即 initClass())。如果您实例化子类。超类必须使用子类指定的覆盖方法。
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();
}
}
我相信 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
... 这表明派生 class 的成员在基 class 的构造函数(以及派生 class 的成员函数的调用之后初始化已完成)。这也证明了从构造函数 调用可重写函数的一个 关键危险,即该函数可以在它所依赖的 class 的成员被初始化之前被调用。出于这个原因,构造函数通常应该避免调用成员函数(并且,当他们这样做时,那些函数应该是 final
或 static
,这样它们要么只依赖于当前的 class已初始化或在 none 个实例变量上)。
发生这种情况是因为在 ClassB
的字段初始化之前调用了 superclass 构造函数。因此调用 initClass()
方法设置 b = "omg!"
但是当超级 class 构造函数 returns、b
被初始化为在 [=10 中声明的值时再次调用=] 即 null
.
调试打个断点一步步走,会发现先把b
设置成null
然后再改成omg!
再回来null
.
截至 JSF 12.5
在示例中您可以看到执行顺序。第一步是调用 Constructor 到 Object
构造函数。
之后发生这种情况:
Next, all initializers for the instance variables of class [...] are executed.
由于您的实例变量 b 被初始化为 null 之后它将再次为 null
之所以会这样,是因为AClass的第一个构造函数设置了b = omg!
和c=gg
的值。之后,当 BClass 加载到内存中时,它设置 b=null
并且 c 保持原样,即 gg,这是因为在 BClass 中,对于 b 你正在做声明和初始化,而对于 c 你正在做只有声明,因为 c 已经在内存中,它甚至不会得到它的默认值,并且因为你没有为 c 做任何初始化,它保持它的早期状态。
关于正在发生的事情,已经给出了几个正确的答案。我只是想补充一点,从构造函数调用重写的方法通常是不好的做法(当然,除非您确切地知道自己在做什么)。如您所见,子类在其实例方法被调用时可能未完全初始化(子类构造函数逻辑尚未执行,因此在未构造的对象上调用有效覆盖的方法是危险的)这可能会导致混淆,例如这个问题中描述的那个。
最好在构造函数中编写初始化逻辑,如果它太长,则将其分成几个从构造函数调用的私有方法。
在下面的示例中,为什么字符串 b 打印 null 而字符串 c 打印 "gg"。
如果我错了,请纠正我,只要子类 (BClass) 覆盖超类 (AClass) 的受保护方法(即 initClass())。如果您实例化子类。超类必须使用子类指定的覆盖方法。
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();
}
}
我相信 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
... 这表明派生 class 的成员在基 class 的构造函数(以及派生 class 的成员函数的调用之后初始化已完成)。这也证明了从构造函数 调用可重写函数的一个 关键危险,即该函数可以在它所依赖的 class 的成员被初始化之前被调用。出于这个原因,构造函数通常应该避免调用成员函数(并且,当他们这样做时,那些函数应该是 final
或 static
,这样它们要么只依赖于当前的 class已初始化或在 none 个实例变量上)。
发生这种情况是因为在 ClassB
的字段初始化之前调用了 superclass 构造函数。因此调用 initClass()
方法设置 b = "omg!"
但是当超级 class 构造函数 returns、b
被初始化为在 [=10 中声明的值时再次调用=] 即 null
.
调试打个断点一步步走,会发现先把b
设置成null
然后再改成omg!
再回来null
.
截至 JSF 12.5
在示例中您可以看到执行顺序。第一步是调用 Constructor 到 Object
构造函数。
之后发生这种情况:
Next, all initializers for the instance variables of class [...] are executed.
由于您的实例变量 b 被初始化为 null 之后它将再次为 null
之所以会这样,是因为AClass的第一个构造函数设置了b = omg!
和c=gg
的值。之后,当 BClass 加载到内存中时,它设置 b=null
并且 c 保持原样,即 gg,这是因为在 BClass 中,对于 b 你正在做声明和初始化,而对于 c 你正在做只有声明,因为 c 已经在内存中,它甚至不会得到它的默认值,并且因为你没有为 c 做任何初始化,它保持它的早期状态。
关于正在发生的事情,已经给出了几个正确的答案。我只是想补充一点,从构造函数调用重写的方法通常是不好的做法(当然,除非您确切地知道自己在做什么)。如您所见,子类在其实例方法被调用时可能未完全初始化(子类构造函数逻辑尚未执行,因此在未构造的对象上调用有效覆盖的方法是危险的)这可能会导致混淆,例如这个问题中描述的那个。
最好在构造函数中编写初始化逻辑,如果它太长,则将其分成几个从构造函数调用的私有方法。