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必须是构造函数中的第一条语句。

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


当前回答

在子类构造函数中添加super()的主要目标是编译器的主要工作是将所有类与Object类建立直接或间接的连接,这就是为什么编译器检查我们是否提供了super(参数化),然后编译器不承担任何责任。 这样所有的实例成员从Object初始化为子类。

其他回答

因为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,导致了这种限制。多年来, 我们已经在虚拟机级别解决了这些问题,直到它变成 考虑取消这一限制是可行的,不仅仅是为了记录, 但对于所有的构造函数。

父类的构造函数需要在子类的构造函数之前调用。这将确保如果在构造函数中调用父类上的任何方法,父类已经正确设置。

你要做的是,将参数传递给超级构造函数是完全合法的,你只需要像你所做的那样内联构造这些参数,或者将它们传递给你的构造函数,然后将它们传递给super:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                super(myArray);
        }
}

如果编译器没有强制执行,你可以这样做:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                someMethodOnSuper(); //ERROR super not yet constructed
                super(myArray);
        }
}

在父类有默认构造函数的情况下,编译器会自动插入对super的调用。由于Java中的每个类都继承自Object,所以必须以某种方式调用Object的构造函数,并且必须首先执行它。编译器可以自动插入super()。强制super首先出现,强制构造函数主体以正确的顺序执行,即:Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

在子类构造函数中添加super()的主要目标是编译器的主要工作是将所有类与Object类建立直接或间接的连接,这就是为什么编译器检查我们是否提供了super(参数化),然后编译器不承担任何责任。 这样所有的实例成员从Object初始化为子类。

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

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

举个例子:

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;
    }
}

我找到了一个变通办法。

这将不会编译:

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

这是可行的:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        this(a + b);
        doSomething2(a);
        doSomething3(b);
    }

    private MySubClass(int c) {
        super(c);
        doSomething(c);
    }
}