覆盖超类的受保护方法

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 的成员被初始化之前被调用。出于这个原因,构造函数通常应该避免调用成员函数(并且,当他们这样做时,那些函数应该是 finalstatic,这样它们要么只依赖于当前的 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 做任何初始化,它保持它的早期状态。

关于正在发生的事情,已经给出了几个正确的答案。我只是想补充一点,从构造函数调用重写的方法通常是不好的做法(当然,除非您确切地知道自己在做什么)。如您所见,子类在其实例方法被调用时可能未完全初始化(子类构造函数逻辑尚未执行,因此在未构造的对象上调用有效覆盖的方法是危险的)这可能会导致混淆,例如这个问题中描述的那个。

最好在构造函数中编写初始化逻辑,如果它太长,则将其分成几个从构造函数调用的私有方法。