Java要求,如果在构造函数中调用this()或super(),它必须是第一条语句。为什么?

例如:

public class MyClass {
    public MyClass(int x) {}
}

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        int c = a + b;
        super(c);  // COMPILE ERROR
    }
}

Sun编译器说,调用super必须是构造函数中的第一条语句。Eclipse编译器说,构造函数调用必须是构造函数中的第一个语句。

然而,你可以通过稍微重新安排代码来解决这个问题:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        super(a + b);  // OK
    }
}

下面是另一个例子:

public class MyClass {
    public MyClass(List list) {}
}

public class MySubClassA extends MyClass {
    public MySubClassA(Object item) {
        // Create a list that contains the item, and pass the list to super
        List list = new ArrayList();
        list.add(item);
        super(list);  // COMPILE ERROR
    }
}

public class MySubClassB extends MyClass {
    public MySubClassB(Object item) {
        // Create a list that contains the item, and pass the list to super
        super(Arrays.asList(new Object[] { item }));  // OK
    }
}

因此,它不会阻止您在调用super()之前执行逻辑。它只是阻止您执行无法放入单个表达式中的逻辑。

调用this()也有类似的规则。编译器说,调用this必须是构造函数中的第一条语句。

为什么编译器有这些限制?你能给出一个代码例子,如果编译器没有这个限制,就会发生不好的事情吗?


当前回答

Java为什么这样做的问题已经有了答案,但由于我无意中发现了这个问题,希望找到一个更好的单行程序的替代品,因此在此分享我的解决方法:

public class SomethingComplicated extends SomethingComplicatedParent {

    private interface Lambda<T> {
        public T run();
    }

    public SomethingComplicated(Settings settings) {
        super(((Lambda<Settings>) () -> {

            // My modification code,
            settings.setting1 = settings.setting2;
            return settings;
        }).run());
    }
}

调用静态函数应该执行得更好,但如果我坚持将代码“置于”构造函数内部,或者如果我必须更改多个参数,并且发现定义许多静态方法不利于可读性,我会使用这种方法。

其他回答

因为JLS是这么说的。是否可以以兼容的方式更改JLS以允许它?是的。

然而,这会使语言规范变得更加复杂,因为语言规范已经足够复杂了。这不是一件非常有用的事情,并且有一些方法可以绕过它(使用静态方法或lambda表达式this(fn())的结果调用另一个构造函数-该方法在另一个构造函数之前调用,因此也是超级构造函数)。所以改变的功率重量比是不利的。

请注意,该规则本身并不阻止在父类完成构造之前使用字段。

想想这些非法的例子。

super(this.x = 5);

super(this.fn());

super(fn());

super(x);

super(this instanceof SubClass);
// this.getClass() would be /really/ useful sometimes.

这个例子是合法的,但却是“错误的”。

class MyBase {
    MyBase() {
        fn();
    }
    abstract void fn();
}
class MyDerived extends MyBase {
    void fn() {
       // ???
    }
}

在上面的例子中,如果MyDerived。fn需要MyDerived构造函数的参数,它们需要通过ThreadLocal进行处理。;(

顺便提一下,自Java 1.4以来,包含外部this的合成字段在调用内部类的超构造函数之前被赋值。这在针对早期版本编译的代码中导致了特殊的NullPointerException事件。

还要注意,在存在不安全发布的情况下,构造可以由其他线程重新排序查看,除非采取了预防措施。

编辑2018年3月:在消息记录:构造和验证中,Oracle建议删除此限制(但与c#不同的是,在构造函数链接之前,这将肯定是未分配的(DU))。

从历史上看,this()或super()在构造函数中必须位于第一个。这 限制从来不受欢迎,被认为是武断的。有 一些微妙的原因,包括验证 invokspecial,导致了这种限制。多年来, 我们已经在虚拟机级别解决了这些问题,直到它变成 考虑取消这一限制是可行的,不仅仅是为了记录, 但对于所有的构造函数。

class C
{
    int y,z;

    C()
    {
        y=10;
    }

    C(int x)
    {
        C();
        z=x+y;
        System.out.println(z);
    }
}

class A
{
    public static void main(String a[])
    {
        new C(10);
    }
}

请看例子,如果我们调用构造函数C(int x),那么z的值取决于y,如果我们不在第一行调用C(),那么这将是z的问题,z将无法得到正确的值。

在调用子对象的构造函数之前,可以使用匿名初始化块初始化子对象中的字段。这个例子将演示:

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

class Parent {
    public Parent() {
        System.out.println("In parent");
    }
}

class Child extends Parent {

    {
        System.out.println("In initializer");
    }

    public Child() {
        super();
        System.out.println("In child");
    }
}

这将输出:

在父 在初始化 在儿童

因此,它不会阻止您在调用之前执行逻辑 超级。它只是阻止你执行你无法适应的逻辑 变成一个表达式。

实际上,你可以用几个表达式执行逻辑,你只需要把你的代码包装在一个静态函数中,然后在超级语句中调用它。

举个例子:

public class MySubClassC extends MyClass {
    public MySubClassC(Object item) {
        // Create a list that contains the item, and pass the list to super
        super(createList(item));  // OK
    }

    private static List createList(item) {
        List list = new ArrayList();
        list.add(item);
        return list;
    }
}

构造函数按照的顺序完成执行是有意义的 推导。因为父类不知道任何子类,任何 它需要执行的初始化与可能的初始化是分开的 子类执行任何初始化的先决条件。 因此,它必须首先完成它的执行。

一个简单的演示:

class A {
    A() {
        System.out.println("Inside A's constructor.");
    }
}

class B extends A {
    B() {
        System.out.println("Inside B's constructor.");
    }
}

class C extends B {
    C() {
        System.out.println("Inside C's constructor.");
    }
}

class CallingCons {
    public static void main(String args[]) {
        C c = new C();
    }
}

这个程序的输出是:

Inside A's constructor
Inside B's constructor
Inside C's constructor