为什么在访问字段之前必须调用超级构造函数?

时间:2013-12-03 09:49:27

标签: java bytecode

我的一个类继承自我使用的框架中的类。超类在其构造函数中调用一个方法,我在自己的类中覆盖它。 该方法使用我希望初始化的字段,超级构造函数调用它以避免NullPointerException。

有没有办法做到这一点?

以下是综合测试方案,我希望c中的Child在调用call时不为空。

public class Test {

    public static class Parent {
        public Parent() {
            super();
            call();
        }

        // only called from parent constructor
        public void call() {
            System.out.println("Parent");
        }
    }

    public static class Child extends Parent {

        private Child c = this;

        public Child() {
            super();
        }

        // only called from parent constructor
        public void call() {
            System.out.println("Child, c is " + (c == null ? "null" : "this"));
        }
    }

    public static void main(String[] args) {
        new Child();
    }
}

在Java 7之前,这是可能的。我可以通过这样的特技来解决这个问题:

    public static class Child extends Parent {

        private Child c;

        private Child(Object unused) {
            super();
        }

        public Child() {
            this(c = this);
        }

        // only called from parent constructor
        public void call() {
            System.out.println("Child, c is " + (c == null ? "null" : "this"));
        }
    }

现在,这将不再适用。我很欣赏额外的安全性,但是来自super的电话会破坏它所获得的任何安全性并降低灵活性。

我想要一种规避这种限制的方法。 作为一种替代方案,我想知道通过限制来获得超级构造函数的情况。

4 个答案:

答案 0 :(得分:3)

将在超类构造函数之前调用静态初始化程序。但是,您将无法设置任何非静态字段,因此很可能无法帮助。

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html

非超级构造函数完成后调用非静态初始化块也没有帮助。

另一种方法可能是在从超级构造函数调用时不执行任何操作,并再次调用子构造函数,例如:

    public Child() {
        super();
        call();
    }

    public void call() {

       if (c==null) {
         return;
       }

       System.out.println("do something with c now");

    }

如果在超级构造函数中发生了更多依赖于此方法的东西,这将无效。

我必须同意EJP的观点,这是一个坏主意;找到一个完全不同的解决方案,不涉及折磨构造函数会好得多。

答案 1 :(得分:2)

请注意,您的类Child由Java编译器转换为以下等效项:

public static class Child extends Parent {

    private Child c;

    public Child() {
        super();
        c = this;
    }

    // Remaining implementation
}

对于Java 6和7,这是相同的,当使用任何两个版本进行编译时,构造函数的生成字节代码甚至相同。在调用超级构造函数之后,始终会实例化本地字段。您使用什么编译器来“解决”工作?

这种限制非常基本。这样,您可以依赖首先应用的超级构造函数。想象一下,您的子构造函数正在使用此类中声明的final字段。如果您不保证此构造函数执行顺序,则无法保证此字段已初始化。这种限制使Java更可靠。

答案 2 :(得分:2)

这是对“我想知道通过超级构造函数案例的限制所获得的内容”的答案。问题的一部分。

在构造过程中,X类中声明的字段可能处于三种状态:所有默认值,全部初始化为一致的工作值,以及其他任何内容。

目标似乎是X以外的类中的代码只能看到前两个状态中的一个。当任何X超类的非静态初始化程序或构造函数代码正在运行时,X的字段都处于默认状态。当X的任何子类的非静态初始化程序或构造函数代码正在运行时,所有X的字段都已初始化为完全一致的可用状态。

只有X初始化程序和构造函数代码必须处理处于不一致状态的X字段,一些是初始化的,一些是默认的,一些是部分初始化的。

通过从X超类初始化器或构造函数调用X方法可以避免这种情况,但这通常被视为反模式。问题是运行的X代码不是从部分构造的X中的初始化程序或构造函数本地调用的。如果该代码更改字段,则在X初始化程序和构造函数体运行时可能会覆盖此更改。

答案 3 :(得分:2)

这应该永远不会起作用。

请注意,在字节码级别,实际上允许这样做。在字节码中,您可以在调用超类构造函数之前设置当前类中声明的字段。但是,Java无法使用此行为。它仅由Java编译器秘密使用,以初始化为支持内部类而添加的合成字段。