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

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


当前回答

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

一个简单的演示:

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

其他回答

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

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

在构造子对象之前,必须先创建父对象。 如你所知,当你这样写类时:

public MyClass {
        public MyClass(String someArg) {
                System.out.println(someArg);
        }
}

它转向下一个(extend和super只是被隐藏了):

public MyClass extends Object{
        public MyClass(String someArg) {
                super();
                System.out.println(someArg);
        }
}

First we create an Object and then extend this object to MyClass. We can not create MyClass before the Object. The simple rule is that parent's constructor has to be called before child constructor. But we know that classes can have more that one constructor. Java allow us to choose a constructor which will be called (either it will be super() or super(yourArgs...)). So, when you write super(yourArgs...) you redefine constructor which will be called to create a parent object. You can't execute other methods before super() because the object doesn't exist yet (but after super() an object will be created and you will be able to do anything you want).

So why then we cannot execute this() after any method? As you know this() is the constructor of the current class. Also we can have different number of constructors in our class and call them like this() or this(yourArgs...). As I said every constructor has hidden method super(). When we write our custom super(yourArgs...) we remove super() with super(yourArgs...). Also when we define this() or this(yourArgs...) we also remove our super() in current constructor because if super() were with this() in the same method, it would create more then one parent object. That is why the same rules imposed for this() method. It just retransmits parent object creation to another child constructor and that constructor calls super() constructor for parent creation. So, the code will be like this in fact:

public MyClass extends Object{
        public MyClass(int a) {
                super();
                System.out.println(a);
        }
        public MyClass(int a, int b) {
                this(a);
                System.out.println(b);
        }
}

正如其他人所说,你可以这样执行代码:

this(a+b);

你也可以像这样执行代码:

public MyClass(int a, SomeObject someObject) {
    this(someObject.add(a+5));
}

但是你不能像这样执行代码,因为你的方法还不存在:

public MyClass extends Object{
    public MyClass(int a) {

    }
    public MyClass(int a, int b) {
        this(add(a, b));
    }
    public int add(int a, int b){
        return a+b;
    }
}

此外,在this()方法链中必须有super()构造函数。你不能像这样创建一个对象:

public MyClass{
        public MyClass(int a) {
                this(a, 5);
        }
        public MyClass(int a, int b) {
                this(a);
        }
}

因为这就是传承哲学。根据Java语言规范,构造函数体是这样定义的:

ConstructorBody: {ExplicitConstructorInvocationopt BlockStatementsopt}

构造函数体的第一个语句可以是任意一个

显式调用同一类的另一个构造函数(通过使用关键字“this”);或 直接超类的显式调用(通过使用关键字"super")

If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object, then the constructor body implicitly begins with a superclass constructor invocation "super();", an invocation of the constructor of its direct superclass that takes no arguments. And so on.. there will be a whole chain of constructors called all the way back to the constructor of Object; "All Classes in the Java platform are Descendants of Object". This thing is called "Constructor Chaining".

为什么会这样? Java以这种方式定义ConstructorBody的原因是,他们需要维护对象的层次结构。记住继承的定义;它扩展了一个类。话虽如此,你不能扩展不存在的东西。首先需要创建基类(超类),然后才能派生它(子类)。这就是为什么他们称它们为父类和子类;你不能没有父母就有孩子。

On a technical level, a subclass inherits all the members (fields, methods, nested classes) from its parent. And since Constructors are NOT members (They don't belong to objects. They are responsible of creating objects) so they are NOT inherited by subclasses, but they can be invoked. And since at the time of object creation only ONE constructor is executed. So how do we guarantee the creation of the superclass when you create the subclass object? Thus the concept of "constructor chaining"; so we have the ability to invoke other constructors (i.e. super) from within the current constructor. And Java required this invocation to be the FIRST line in the subclass constructor to maintain the hierarchy and guarantee it. They assume that if you don't explicitly create the parent object FIRST (like if you forgot about it), they will do it implicitly for you.

该检查在编译期间进行。但是我不确定在运行时会发生什么,我们会得到什么样的运行时错误,如果Java没有抛出一个编译错误,当我们显式地试图从子类的构造函数中执行一个基本构造函数时,在它的主体中间,而不是从第一行开始……

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

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

这是官方回放: 从历史上看,this()或super()在构造函数中必须位于第一个。这 限制从来不受欢迎,被认为是武断的。有一个 一些微妙的原因,包括验证调用特殊, 这导致了这种限制。这些年来,我们已经解决了 这些都是虚拟机级别的,直到它变得实用 考虑取消这一限制,不只是对记录,而是对所有人 构造函数。