为什么Java编译器(javac)有时候是愚蠢的,我确定Java编译器只是一个程序,但有时被设计为愚蠢的(有时候不是),我也是Java的粉丝:)
class Child extends Base {
Child() {
int i = 5; // any statement here.
super(i);
}
}
class Base{
Base(int i) {
}
}
在这里编译器,声称第一个语句应该是对继承类的构造函数的调用,但是如果我们将这个第一个语句包装在一个静态方法中,它的工作正常!!!!!!
class Child extends Base {
Child() {
super(foo()); // works fine!!!!
}
static int foo(){
return 5;
}
}
这很好!!!!!!!,另一个杀手示例:
Child() {
try{
super(5);
}catch(Exception e){}
}
尝试捕获语言功能!!!
我知道编译器要求我们调用超类型构造函数,因为它应该在自身对象之前初始化继承的对象(通过对象链接释放Generaly Java的继承),但我认为编译器应该有点聪明,它应该让我们操纵代码,而我们在调用它的超级构造函数之前没有触及对象,所以必须工作:
Child() {
int i = 5; // this MUST BE acceptable since we didn't touch
// any current object or inherited field or we didn't
// call any method on it.
super(i);
}
但这不应该起作用:
class Child extends Base {
private int i;
Child() {
i = 6; // this shouldn't work (its clear why).
super();
}
}
我只想了解为什么没有实现这一点,特别是当我看到Java可以捕获无法访问的代码(一个智能功能)时,所以20多年来,Java并没有提供这样的基本功能,因为通常这个有时会使代码变得更难看,有时我们必须制作愚蠢的静态方法来避免这种情况,有时候,我们只是调用超级构造函数(对于javac闭包)然后我们重新初始化继承的字段!!!!!!! !
虽然,我不认为,这是JVM和字节码的问题,我认为这只能在javac中实现:)
我真的很喜欢Java,但是这个让我很生气,我忘了为下一个版本(Java 9)建议这个,我希望它将包含在Java 10中,我们等待3年以上,比没有更好它根本就是:'(
答案 0 :(得分:2)
两个答案都完全正确。我担心OP想知道这种行为背后的原因。
这就是为什么调用super()必须是构造函数中的第一个,并且在调用super()之前不能执行任何操作。
int i = 5;
不起作用,因为你可以制作一个更棘手的初始化:
int i=someMethodCall();
如果允许的话,显然可能使用任何未初始化的继承字段。
static int foo(){
return 5;
}
有效,因为它是一个静态方法 - 它不依赖于对象实例的字段(也看不到字段) - 所以你可以在super()之前调用它;
Child() {
try{
super(5);
}catch(Exception e){}
}
不起作用,因为不能保证再调用super()。 Try..catch将允许super(5)抛出异常(而不是初始化继承的字段),而捕获和忽略此异常将产生一个Child对象,其中继承的字段根本没有初始化。
虽然行为可能听起来很愚蠢 - 实际上它完全有道理,不是吗?
答案 1 :(得分:2)
我认为这可能主要是基于意见的。
我看待它的方式,保持简单有时是更智能的设计方式。
让编译器接受编译器当前不接受的某些代码,这将(至少)需要更多复杂性。编译器将有额外的负担来确定可以允许哪些结构,以及哪些结构不允许。编译器足够复杂。为何添加更多不必要的复杂特别是如果额外的复杂性无法解决或帮助我们解决某个特定问题。
要求超类中的构造函数在子类的构造函数中的任何代码之前运行,以保持更简单。
声明一个静态方法,如OP示例所示,不违反有关在超类中运行构造函数的规则。永远不会实例化静态方法。它是类定义的一部分,它不是类的实例的一部分。
我认为使编译器更智能(处理OP提出的Java代码)根本不是一个明智的决定。
问:对编译器的拟议更改解决了什么真正的问题?
问:问题可能会产生什么问题?有时选择避免额外的复杂性(并可能产生更多问题)是更明智的选择。
答案 2 :(得分:0)
也许这会给你一些洞察力。无论你做什么,超级类的构造函数都将首先调用始终。
class Parent {
Parent(int x) {
System.out.println("Parent");
}
}
class Child extends Parent {
Child () {
super(foo());
System.out.println("Child");
}
public static void main(String[] args) {
Child a = new Child(); // prints Parent\nChild
Parent b = new Child(); // prints Parent\nChild
}
static int foo() {
return 5;
}
}
正如您所看到的,无论您做什么,Parent
构造函数总是首先被调用。这可以确保如果在构造函数中调用Parent
类上的任何方法,则Parent
类已经正确设置。如果我在调用System.out.println("Child")
之前尝试移动super()
行,则会严格执行此属性这一事实。
来自[JLS 8.8.7.1],
让C成为实例化的类,让S成为直接的 C的超类。
如果S不可访问,则为编译时错误(第6.6节)。
如果超类构造函数调用语句是合格的,则:
If S is not an inner class, or if the declaration of S occurs in a static context, then a compile-time error occurs. Otherwise, let p be the Primary expression immediately preceding ".super". Let O be the innermost lexically enclosing class of S. It is a compile-time error if the type of p is not O or a subclass of O, or if the type of p is not accessible (§6.6).
如果超类构造函数调用语句不合格,则 如果S是内部成员类,那么如果S是,则编译时错误 通过声明或不是词汇封闭的C类成员 继承。
答案 3 :(得分:0)
如果构造函数体不是以显式构造函数调用开始并且声明的构造函数不是原始类Object的一部分,那么构造函数体隐式地以超类构造函数调用开始" super();&# 34;,调用其直接超类的构造函数,不带参数。
来自Java规范,here.
他们正在积极执行此事。
以下是解析规则:
ConstructorBody:
{ [ExplicitConstructorInvocation] [BlockStatements] }
ExplicitConstructorInvocation:
[TypeArguments] this ( [ArgumentList] ) ;
[TypeArguments] super ( [ArgumentList] ) ;
ExpressionName . [TypeArguments] super ( [ArgumentList] ) ;
Primary . [TypeArguments] super ( [ArgumentList] ) ;
TypeArguments:
< TypeArgumentList >
ArgumentList:
Expression {, Expression}