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必须是构造函数中的第一条语句。
为什么编译器有这些限制?你能给出一个代码例子,如果编译器没有这个限制,就会发生不好的事情吗?
我已经通过链接构造函数和静态方法找到了解决这个问题的方法。我想做的是这样的:
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;
}
}
基本上是根据构造函数的形参构造一个对象,将对象存储在成员中,并将该对象的方法的结果传递到super的构造函数中。使成员为final也是相当重要的,因为类的性质是不可变的。注意,构造Bar实际上需要一些中间对象,因此在我的实际用例中,它不能简化为一行程序。
我最终做出了这样的工作:
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以允许它?是的。
然而,这会使语言规范变得更加复杂,因为语言规范已经足够复杂了。这不是一件非常有用的事情,并且有一些方法可以绕过它(使用静态方法或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.
这个例子是合法的,但却是“错误的”。
class MyBase {
MyBase() {
fn();
}
abstract void fn();
}
class MyDerived extends MyBase {
void fn() {
// ???
}
}
在上面的例子中,如果MyDerived。fn需要MyDerived构造函数的参数,它们需要通过ThreadLocal进行处理。;(
顺便提一下,自Java 1.4以来,包含外部this的合成字段在调用内部类的超构造函数之前被赋值。这在针对早期版本编译的代码中导致了特殊的NullPointerException事件。
还要注意,在存在不安全发布的情况下,构造可以由其他线程重新排序查看,除非采取了预防措施。
编辑2018年3月:在消息记录:构造和验证中,Oracle建议删除此限制(但与c#不同的是,在构造函数链接之前,这将肯定是未分配的(DU))。
从历史上看,this()或super()在构造函数中必须位于第一个。这
限制从来不受欢迎,被认为是武断的。有
一些微妙的原因,包括验证
invokspecial,导致了这种限制。多年来,
我们已经在虚拟机级别解决了这些问题,直到它变成
考虑取消这一限制是可行的,不仅仅是为了记录,
但对于所有的构造函数。