为什么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中的每个类都是从Object
inheritance的,因此必须以某种方式调用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将无法获得正确的值。