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

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


当前回答

我完全同意,限制太严格了。使用静态辅助方法(如Tom Hawtin - tackline所建议的)或将所有“pre-super()计算”推入参数中的单个表达式并不总是可行的,例如:

class Sup {
    public Sup(final int x_) { 
        //cheap constructor 
    }
    public Sup(final Sup sup_) { 
        //expensive copy constructor 
    }
}

class Sub extends Sup {
    private int x;
    public Sub(final Sub aSub) {
        /* for aSub with aSub.x == 0, 
         * the expensive copy constructor is unnecessary:
         */

         /* if (aSub.x == 0) { 
          *    super(0);
          * } else {
          *    super(aSub);
          * } 
          * above gives error since if-construct before super() is not allowed.
          */

        /* super((aSub.x == 0) ? 0 : aSub); 
         * above gives error since the ?-operator's type is Object
         */

        super(aSub); // much slower :(  

        // further initialization of aSub
    }
}

正如Carson Myers所建议的那样,使用“尚未构造的对象”异常会有所帮助,但是在每个对象构造期间检查这个异常会减慢执行速度。我希望Java编译器能够更好地区分(而不是不合理地禁止if语句,但允许在形参中使用?-操作符),即使这会使语言规范复杂化。

其他回答

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

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

这将输出:

在父 在初始化 在儿童

你问为什么,其他的答案,在我看来,并没有真正说为什么它是可以调用你的超级构造函数,但只有当它是第一行。原因是您并没有真正调用构造函数。在c++中,等价的语法是

MySubClass: MyClass {

public:

 MySubClass(int a, int b): MyClass(a+b)
 {
 }

};

When you see the initializer clause on its own like that, before the open brace, you know it's special. It runs before any of the rest of the constructor runs and in fact before any of the member variables are initialized. It's not that different for Java. There's a way to get some code (other constructors) to run before the constructor really starts, before any members of the subclass are initialized. And that way is to put the "call" (eg super) on the very first line. (In a way, that super or this is kind of before the first open brace, even though you type it after, because it will be executed before you get to the point that everything is fully constructed.) Any other code after the open brace (like int c = a + b;) makes the compiler say "oh, ok, no other constructors, we can initialize everything then." So it runs off and initializes your super class and your members and whatnot and then starts executing the code after the open brace.

如果几行之后,它遇到一些代码说“哦,是的,当你构造这个对象时,这是我想让你传递给基类构造函数的参数”,那就太晚了,这没有任何意义。所以你会得到一个编译器错误。

我相当肯定(那些熟悉Java规范的人会说),它是为了防止你(a)被允许使用部分构造的对象,(b)强迫父类的构造函数在一个“新”对象上构造。

“坏”事情的一些例子是:

class Thing
{
    final int x;
    Thing(int x) { this.x = x; }
}

class Bad1 extends Thing
{
    final int z;
    Bad1(int x, int y)
    {
        this.z = this.x + this.y; // WHOOPS! x hasn't been set yet
        super(x);
    }        
}

class Bad2 extends Thing
{
    final int y;
    Bad2(int x, int y)
    {
        this.x = 33;
        this.y = y; 
        super(x); // WHOOPS! x is supposed to be final
    }        
}

因为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 Good {
    int essential1;
    int essential2;

    Good(int n) {
        if (n > 100)
            throw new IllegalArgumentException("n is too large!");
        essential1 = 1 / n;
        essential2 = n + 2;
    }
}

class Bad extends Good {
    Bad(int n) {
        try {
            super(n);
        } catch (Exception e) {
            // Exception is ignored
        }
    }

    public static void main(String[] args) {
        Bad b = new Bad(0);
//        b = new Bad(101);
        System.out.println(b.essential1 + b.essential2);
    }
}

An exception during construction almost always indicates that the object being constructed could not be properly initialized, now is in a bad state, unusable, and must be garbage collected. However, a constructor of a subclass has got the ability to ignore an exception occurred in one of its superclasses and to return a partially initialized object. In the above example, if the argument given to new Bad() is either 0 or greater than 100, then neither essential1 nor essential2 are properly initialized.

你可能会说忽略异常总是一个坏主意。好的,这里还有一个例子:

class Bad extends Good {
    Bad(int n) {
        for (int i = 0; i < n; i++)
            super(i);
    }
}

很有趣,不是吗?在这个例子中我们创建了多少个对象?一个?两个?或者什么都没有……

允许在构造函数中间调用super()或this()将打开一个令人讨厌的构造函数的潘多拉盒子。


另一方面,我理解在调用super()或this()之前经常需要包含一些静态部分。这可能是任何不依赖于此引用的代码(实际上,它已经存在于构造函数的最开始,但在super()或This()返回之前不能有序使用),并且需要进行这样的调用。此外,像在任何方法中一样,在调用super()或this()之前创建的一些局部变量可能会在调用super()或this()之后被需要。

在这种情况下,你有以下机会:

Use the pattern presented at this answer, which allows to circumvent the restriction. Wait for the Java team to allow pre-super() and pre-this() code. It may be done by imposing a restriction on where super() or this() may occur in a constructor. Actually, even today's compiler is able to distinguish good and bad (or potentially bad) cases with the degree enough to securely allow static code addition at the beginning of a constructor. Indeed, assume that super() and this() return this reference and, in turn, your constructor has

return this;

最后。以及编译器拒绝代码

public int get() {
    int x;
    for (int i = 0; i < 10; i++)
        x = i;
    return x;
}

public int get(int y) {
    int x;
    if (y > 0)
        x = y;
    return x;
}

public int get(boolean b) {
    int x;
    try {
        x = 1;
    } catch (Exception e) {
    }
    return x;
}

with the error "variable x might not have been initialized", it could do so on this variable, making its checks on it just like on any other local variable. The only difference is this cannot be assigned by any means other than super() or this() call (and, as usual, if there is no such call at a constructor, super() is implicitly inserted by compiler in the beginning) and might not be assigned twice. In case of any doubt (like in the first get(), where x is actually always assigned), the compiler could return an error. That would be better than simply return error on any constructor where there is something except a comment before super() or this().