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:

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:

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

你问为什么,其他的答案,在我看来,并没有真正说为什么它是可以调用你的超级构造函数,但只有当它是第一行。原因是您并没有真正调用构造函数。在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.

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

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

一个简单的演示:

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

Tldr:

其他的答案都解决了这个问题的“为什么”。我将提供一个关于这个限制的hack:

基本思想是用嵌入式语句劫持超级语句。这可以通过将语句伪装成表达式来实现。

Tsdr:

假设我们想在调用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;
    }
}

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

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将无法得到正确的值。