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

Java 为什么this()和super()必须是构造函数中的第一条语句?,java,constructor,Java,Constructor,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 }

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
    }
}
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
    }
}
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;
    }
}
class Bad extends Good {
    Bad(int n) {
        for (int i = 0; i < n; i++)
            super(i);
    }
}
Sun编译器说“对super的调用必须是构造函数中的第一个语句”。Eclipse编译器说“构造函数调用必须是构造函数中的第一条语句”

但是,您可以通过稍微重新安排代码来解决此问题:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        super(a + b);  // OK
    }
}
下面是另一个例子:

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
    }
}
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
    }
}
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;
    }
}
class Bad extends Good {
    Bad(int n) {
        for (int i = 0; i < n; i++)
            super(i);
    }
}
因此,它不会阻止您在调用super之前执行逻辑。它只是阻止您执行无法放入单个表达式的逻辑

调用
this()
也有类似的规则。编译器说“对它的调用必须是构造函数中的第一条语句”


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

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

您要做的是,将参数传递给超级构造函数是完全合法的,您只需要在执行操作时内联构造这些参数,或者将它们传递给构造函数,然后将它们传递给
super

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                super(myArray);
        }
}
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
    }
}
如果编译器未强制执行此操作,则可以执行以下操作:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                someMethodOnSuper(); //ERROR super not yet constructed
                super(myArray);
        }
}
父类
具有默认构造函数的情况下,
编译器会自动插入对super的调用。由于Java中的每个类都继承自
对象
,因此必须以某种方式调用对象构造函数,并且必须首先执行它。编译器自动插入super()允许这样做。强制super首先出现,强制构造函数主体按照正确的顺序执行,即:Object->Parent->Child->ChildOfChild->SoOnSoForth

我相当确信(熟悉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
    }        
}

在调用子项的构造函数之前,可以使用匿名初始值设定项块初始化子项中的字段。此示例将演示:

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");
    }
}
这将输出:

在父级中
在初始值设定项中
在儿童中

因为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.
这个例子是合法的,但“错误的”

在上面的示例中,如果
MyDerived.fn
需要来自
MyDerived
构造函数的参数,则需要使用
ThreadLocal
对这些参数进行筛选;(

顺便说一句,自Java 1.4以来,包含外部
this
的合成字段在调用内部类超级构造函数之前被赋值。这导致编译为针对早期版本的代码中出现特殊的
NullPointerException
事件

另外请注意,如果存在不安全的出版物,除非采取预防措施,否则可以通过其他线程重新排序来查看构造

编辑2018年3月:Oracle在消息中建议删除此限制(但与C#不同,
在构造函数链接之前肯定是未分配的(DU))

历史上,this()或super()必须是构造函数中的第一个 限制从来都不受欢迎,被认为是武断的 一些微妙的原因,包括 特别是,这导致了这种限制。多年来, 我们已经在虚拟机级别解决了这些问题,直到它成为 切实考虑取消这种限制,不仅仅是为了记录, 但是对于所有的构造函数


我完全同意,限制太强。使用静态辅助方法(如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语句,但在参数中允许?-运算符)即使这个复杂化了语言规范,

你问为什么,而其他答案,IMO,并没有真正地说为什么调用你的超级构造函数是可以的,但是只有当它是第一行的时候。原因是你并没有真正调用构造函数。在C++中,等效语法是

MySubClass: MyClass {

public:

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

};
当您看到initializer子句本身时,在打开大括号之前,您知道它是特殊的。它在任何其他构造函数运行之前运行,事实上在任何成员变量初始化之前运行。对于Java来说没有什么不同。有一种方法可以获得一些代码(其他构造函数)在构造函数真正启动之前,在子类的任何成员初始化之前运行。这种方法是将“调用”(例如
super
)放在第一行。(在某种程度上,就是
super
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);
    }
}
public T run(Object... args);
super(new InfoRunnable<ThingToPass>() {
    public ThingToPass run(Object... args) {
        /* do your things here */
    }
}.run(/* args here */));
public MyClass {
        public MyClass(String someArg) {
                System.out.println(someArg);
        }
}
public MyClass extends Object{
        public MyClass(String someArg) {
                super();
                System.out.println(someArg);
        }
}
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;
    }
}
public MyClass{
        public MyClass(int a) {
                this(a, 5);
        }
        public MyClass(int a, int b) {
                this(a);
        }
}
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
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);
    }
}
class Bad extends Good {
    Bad(int n) {
        for (int i = 0; i < n; i++)
            super(i);
    }
}
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;
}
public class SomethingComplicated extends SomethingComplicatedParent {

    private interface Lambda<T> {
        public T run();
    }

    public SomethingComplicated(Settings settings) {
        super(((Lambda<Settings>) () -> {

            // My modification code,
            settings.setting1 = settings.setting2;
            return settings;
        }).run());
    }
}