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

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

所以,在超级调用之前,并没有阻止你执行逻辑 。 它只是阻止你执行不能适合单个expression式的逻辑。

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

为什么编译器有这些限制? 你能给一个代码示例,如果编译器没有这个限制,会发生什么坏事?

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

你正在做什么,将parameter passing给超级构造函数是完全合法的,你只需要像你在做的那样内联地构造这些参数,或者把它们传递给你的构造函数,然后传递给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); } } 

parent类具有默认构造函数的情况下, compiler会自动为您插入对super的调用。 由于Java中的每个类都是从Objectinheritance的,因此必须以某种方式调用Object构造函数,并且必须先执行它。 编译器自动插入super()允许这样做。 强制超级先出现,强制构造函数体执行的顺序是:Object – > Parent – > Child – > ChildOfChild – > SoOnSoForth

我通过链接构造函数和静态方法find了解决这个问题的方法。 我想做的事情是这样的:

 public class Foo extends Baz { private final Bar myBar; public Foo(String arg1, String arg2) { // ... // ... Some other stuff needed to construct a 'Bar'... // ... final Bar b = new Bar(arg1, arg2); super(b.baz()): myBar = b; } } 

所以基本上是基于构造函数参数构造一个对象,将该对象存储在成员中,并将该对象的方法的结果传递给超类的构造函数。 让成员最后决定也是相当重要的,因为这个类的性质是不变的。 请注意,实际上,构buildBar实际上需要一些中间对象,所以在我的实际使用情况中不能简化为一行。

我最终使它的工作如下所示:

 public class Foo extends Baz { private final Bar myBar; private static Bar makeBar(String arg1, String arg2) { // My more complicated setup routine to actually make 'Bar' goes here... return new Bar(arg1, arg2); } public Foo(String arg1, String arg2) { this(makeBar(arg1, arg2)); } private Foo(Bar bar) { super(bar.baz()); myBar = bar; } } 

法定代码,它在调用超级构造函数之前完成执行多个语句的任务。

因为JLS这样说。 JLS可以以兼容的方式进行更改吗? 对。 但是,这会使语言规范复杂化,这已经足够复杂了。 这不是一个非常有用的事情,有办法绕过它(调用另一个构造函数的方法this(fn()) – 该方法在另一个构造函数之前调用,因此也超级构造函数) 。 所以做这个改变的权重比是不利的。

我相当肯定(那些熟悉Java规范的人),它是为了防止你被允许使用一个部分构造的对象,和(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 } } 

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

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

当你看到像这样的初始化子句时,在开放的大括号之前,你知道它是特殊的。 它在构造函数的其余部分运行之前运行,事实上在任何成员variables被初始化之前运行。 对于Java来说没有什么不同。 在构造函数真正启动之前,有一种方法可以让一些代码(其他构造函数)在子类的任何成员被初始化之前运行。 那就是把“呼叫”(例如super )放在第一行。 (从某种意义上说, super或者在第一个大括号之前是这样的,尽pipe你之后input了它,因为它会在你完成所有事情之前被执行)。 (比如int c = a + b; )让编译器说:“哦,好吧,没有其他的构造函数,我们可以初始化所有东西。” 所以它运行并初始化你的超类和你的成员什么的,然后开始大括号后面开始执行代码。

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

只是因为这是inheritance的哲学。 而根据Java语言规范,这是如何定义构造函数的主体:

ConstructorBody:{ExplicitConstructorInvocation opt BlockStatements opt }

构造函数体的第一个陈述可能是:
– 显式调用同一类的另一个构造函数(使用关键字“this”)
– 直接超类(通过使用关键字“超级”)

如果一个构造函数体没有以一个明确的构造函数调用开始,并且被声明的构造函数不是原始类Object的一部分,那么构造函数体隐式地从超类构造函数调用“super();”开始,调用构造函数它的直接超类,没有任何争论。 依此类推,将会有一整个构造函数链被调用回Object的构造函数。 “Java平台中的所有类都是对象的后代”。 这个东西叫做“ 构造器链接 ”。

那为什么呢?
Java之所以这样定义ConstructorBody,是因为它需要维护对象的层次结构 。 记住inheritance的定义; 它正在扩展一个类。 说这话,你不能延伸一些不存在的东西。 需要首先创build基类(超类),然后可以派生它(子类)。 这就是为什么他们叫他们父母和孩子类; 没有父母的孩子不能生育。

在技​​术层面上,子类inheritance父类的所有成员(字段,方法,嵌套类)。 而且由于构造函数不是成员(它们不属于对象,它们负责创build对象),所以它们不被子类inheritance,但可以被调用。 而且由于在创build对象的时候只有一个构造函数被执行 。 那么在创build子类对象时,我们如何保证创build超类呢? 因此“构造链”的概念; 所以我们有能力从当前构造函数中调用其他构造函数(即超级)。 而且Java需要这个调用成为子类构造函数中的第一行来维护层次并保证它。 他们假设如果你没有明确地创build父对象FIRST(就像你忘了它),他们会为你隐式的做。

这个检查在编译期间完成。 但是我不确定在运行时会发生什么,我们会得到什么样的运行时错误,当我们明确地尝试从子类的构造函数中执行一个基构造函数时,Java不会抛出编译错误身体,而不是从第一线…

我完全同意,限制太强。 使用静态辅助方法(如Tom Hawtin – tacklinebuild议)或将所有“pre-super()计算”推到参数中的单个expression式中并不总是可能的,例如:

 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 Myersbuild议的那样,使用“未构造的对象”exception会有所帮助,但在每个对象构造过程中检查这个exception将会减慢执行速度。 我希望Java编译器能够更好地区分(而不是忽略if语句,但允许参数中的?-operator),即使这会使语言规范复杂化。

所以,在超级调用之前,并没有阻止你执行逻辑。 它只是阻止你执行不能适合单个expression式的逻辑。

实际上,你可以执行几个执行的逻辑,你只需要把你的代码封装在一个静态函数中,然后在超级语句中调用它。

用你的例子:

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

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

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

这将输出:

在父母
在初始化器中
在孩子

我的猜测是,他们这样做是为了让编写处理Java代码的工具的人们更容易,而读取Java代码的人也更less。

如果允许super()或this()调用四处移动,则需要检查更多变体。 例如,如果将super()或this()调用移动到条件if(),它可能必须足够聪明才能将隐式super()插入到else中。 如果您调用super()两次,或者同时使用super()和this(),则可能需要知道如何报告错误。 它可能需要禁止接收方的方法调用,直到super()或this()被调用,并确定何时变得复杂。

让每个人做这些额外的工作可能似乎是一个比收益更大的成本。

我发现了一个动物。

这不会编译:

 public class MySubClass extends MyClass { public MySubClass(int a, int b) { int c = a + b; super(c); // COMPILE ERROR doSomething(c); doSomething2(a); doSomething3(b); } } 

这工作:

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

构造器按推导顺序完成执行是有意义的。 因为超类没有任何子类的知识,所以它需要执行的任何初始化与子类执行的任何初始化都是分离的,也可能是先决条件。 因此,它必须先完成执行。

一个简单的示范:

 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:

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

基本的想法是用embedded式语句劫持 super语句。 这可以通过将您的语句伪装成expression式来完成。

TSDR:

考虑在调用super()之前,我们希望将Statement1()Statement9() super()

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

唯一的限制是父类必须有一个至less需要一个参数的构造函数,以便我们可以作为expression式潜入我们的语句中。

这是一个更详细的例子:

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

事实上,编译器可以为我们自动完成这个过程。 他们只是select不去。

我知道我晚了一点,但我已经使用了这个技巧几次(我知道这有点不寻常):

我用一种方法创build一个通用接口InfoRunnable<T>

 public T run(Object... args); 

如果我需要做一些事情,然后传递给构造函数,我只是这样做:

 super(new InfoRunnable<ThingToPass>() { public ThingToPass run(Object... args) { /* do your things here */ } }.run(/* args here */)); 

在构build子对象之前,必须创build父对象。 正如你所知,当你写这样的课时:

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

它转向下一个(扩展和超级只是隐藏):

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

首先我们创build一个Object ,然后将这个对象扩展到MyClass 。 我们不能在Object之前创buildMyClass 。 简单的规则是父级的构造函数必须在子构造函数之前被调用。 但是我们知道类可以有更多的构造函数。 Java允许我们select一个将被调用的构造函数(它将是super()或者super(yourArgs...) )。 所以,当你写super(yourArgs...)你重新定义构造函数将被调用来创build一个父对象。 你不能在super()之前执行其他的方法,因为这个对象还不存在(但是在super()之后会创build一个对象,你就可以做任何你想做的事情)。

那么为什么我们不能在任何方法之后执行this()呢? 如你所知, this()是当前类的构造函数。 我们也可以在我们的类中有不同数量的构造函数,并将它们称为this()this(yourArgs...) 。 正如我所说的每个构造函数都有隐藏方法super() 。 当我们写自定义super(yourArgs...)时,我们用super(yourArgs...)移除super() super(yourArgs...) 。 同样,当我们定义this()this(yourArgs...)我们也移除了当前构造函数中的super() ,因为如果super() this()在同一个方法中使用this() ,则会创build多个父对象。 这就是为什么对this()方法施加相同的规则。 它只是将父对象创build重新传递给另一个子构造函数,构造函数调用父类创build的super()构造函数。 所以,代码将会是这样的:

 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()构造函数。 你不能像这样创build一个对象:

 public MyClass{ public MyClass(int a) { this(a, 5); } public MyClass(int a, int b) { this(a); } } 
 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将无法获得正确的值。