为什么在构造函数中this()和super()必须是第一个语句?

723

为什么在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
    }
}

太阳编译器提示:call to super must be first statement in constructor。Eclipse编译器提示:Constructor call must be the first statement in a constructor

然而,您可以通过稍微调整代码来解决这个问题:

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()也有类似的规则。编译器会报错:call to this must be first statement in constructor

为什么编译器有这些限制?您能否给出一个代码示例,如果编译器没有此限制,会发生什么糟糕的事情?


9
一个好问题。我在http://valjok.blogspot.com/2012/09/super-constructor-must-be-first.html和programmers.exchange上开始了类似的讨论,展示了有些情况下必须先初始化子字段而不是super()。因此,这种特性增加了做事情的复杂性,而是否对“代码安全性”产生积极影响的问题并不清楚是否能够抵消负面影响。是的,super始终排第一也会有负面后果。令人惊讶的是没有人提到过这一点。我认为这是一个概念性的问题,应该在程序员交流平台上询问。 - Val
60
最糟糕的是,这完全是Java语言的限制。在字节码层面上并没有这样的限制。 - Antimony
2
在字节码级别上实施这样的限制是不可能的 - 即使将所有逻辑都塞进一个表达式中的示例也将违反这样的限制。 - celticminstrel
可能是[调用super()必须是构造函数主体中的第一条语句]的重复问题(https://dev59.com/rYPba4cB1Zd3GeqPzO8Q)。 - Chandrahas Aroori
22个回答

0

简述:

其他答案已经解决了这个问题的“为什么”。我将提供一个绕过此限制的技巧

基本思路是使用嵌入式语句来劫持 super 语句。这可以通过将您的语句伪装成 表达式 来实现。

详述:

假设我们想在调用 super() 之前执行 Statement1()Statement9()

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        super(_1, _2, _3); // compiler rejects because this is not the first line
    }
}

编译器当然会拒绝我们的代码。因此,我们可以这样做:
// This compiles fine:

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        super(F(_1), _2, _3);
    }

    public static T1 F(T1 _1) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        return _1;
    }
}

唯一的限制是,父类必须有一个至少带一个参数的构造函数,这样我们才能将语句作为表达式插入其中。
以下是更详细的示例:
public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        i = i * 10 - 123;
        if (s.length() > i) {
            s = "This is substr s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        Object obj = Static_Class.A_Static_Method(i, s, t1);
        super(obj, i, "some argument", s, t1, t2); // compiler rejects because this is not the first line
    }
}

重新制作为:

// This compiles fine:

public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        super(Arg1(i, s, t1), Arg2(i), "some argument", Arg4(i, s), t1, Arg6(i, t1));
    }

    private static Object Arg1(int i, String s, T1 t1) {
        i = Arg2(i);
        s = Arg4(s);
        return Static_Class.A_Static_Method(i, s, t1);
    }

    private static int Arg2(int i) {
        i = i * 10 - 123;
        return i;
    }

    private static String Arg4(int i, String s) {
        i = Arg2(i);
        if (s.length() > i) {
            s = "This is sub s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        return s;
    }

    private static T2 Arg6(int i, T1 t1) {
        i = Arg2(i);
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        return t2;
    }
}

事实上,编译器本可以为我们自动化这个过程。只是它们选择不这样做。

1
在第二个代码块中,super(F(), _2, _3); 应该改为 super(F(_1), _2, _3); - ADTC
“父类必须有一个至少带一个参数的构造函数”这种说法并不准确——你可以在自己的类中创建另一个带参数的构造函数。 - Aleksandr Dubinsky

0
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将会出现问题,无法获得正确的值。请参考示例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接