Java 编译器愚蠢 - 构造函数

Java compiler stupidity - Constructors

为什么Java编译器(javac)有时是愚蠢的,当然我知道Java编译器只是一个程序,但有时被设计成愚蠢的(有时不是),我也是Java 的粉丝:)

class Child extends Base {
    Child() {
        int i = 5; // any statement here.
        super(i);
    }
}

class Base{
    Base(int i) {
    }
}

这里的编译器,声称第一个语句应该是对继承 class 的构造函数的调用,但是如果我们将这个第一个语句包装在一个静态方法中,它就可以正常工作!!!!!!!

class Child extends Base {
    Child() {
        super(foo()); // works fine!!!!
    }

    static int foo(){
        return 5;
    }
}

这很好用!!!!!!!,另一个杀手级的例子:

    Child() {
        try{
            super(5);
        }catch(Exception e){}
    }

try catch 是语言功能!!!

我知道编译器要求我们调用超类型构造函数,因为它应该在自身对象之前初始化继承对象(通常 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 没有' 提供这样一个 BASIC 功能,因为通常这有时会使代码变得更丑陋,有时我们不得不制作愚蠢的静态方法来避免这种情况,其他时候,我们只是调用超级构造函数(对于 javac 闭嘴)然后我们重新初始化继承的场!!!!!!!!

尽管我不认为,这是 JVM 和字节码的问题,我认为这只能在 javac 中实现:)

我真的很喜欢Java,但是这个让我很生气,我忘了建议下一个版本(Java 9),我希望它会包含在Java 10,我们再等 3 年,总比没有好 :'(

也许这会提供一些见解。不管你做什么,超级 class 的构造函数将 总是 首先被调用。

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 class 上的任何方法,则 Parent class 已经正确设置。如果我尝试在调用 super().

之前移动 System.out.println("Child") 行,编译器错误会显示严格执行此 属性 的事实

来自 [JLS 8.8.7.1],

Let C be the class being instantiated, and let S be the direct superclass of C.

It is a compile-time error if S is not accessible (§6.6).

If a superclass constructor invocation statement is qualified, then:

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).

If a superclass constructor invocation statement is unqualified, and if S is an inner member class, then it is a compile-time error if S is not a member of a lexically enclosing class of C by declaration or inheritance.

If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object, then the constructor body implicitly begins with a superclass constructor invocation "super();", an invocation of the constructor of its direct superclass that takes no arguments.

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

两个答案完全正确。恐怕 OP 想知道这种行为背后的原因。

  • 每个对象都有一个构造函数
  • 每个对象都继承自 Object - 所以一切都有一个父对象
  • 在每个构造函数中,继承的父级被调用 - 隐式(如果你不调用 super)或显式 - 如果你调用 super
  • 在您可以对 您的 对象执行任何操作之前,必须确保 inherited 对象已正确初始化。

这就是为什么必须在构造函数中首先调用 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 对象。

虽然这种行为听起来很愚蠢 - 实际上它完全有道理,不是吗?

我认为这可能主要是基于意见。

在我看来,保持简单有时是更聪明的设计方式。

要让编译器接受一些编译器当前不接受的代码,(至少)需要更多的复杂性。编译器将有额外的负担来确定哪些构造可以被允许,哪些构造不允许。编译器足够复杂。为什么要增加更多不必要的复杂性。特别是如果额外的复杂性不能解决或帮助我们解决特定问题。

要求 superclass 中的构造函数在 subclass 的构造函数中的任何代码之前运行的要求使事情变得 更简单 .

声明一个静态方法,如 OP 示例所示,并不违反关于 运行 超级 class 中的构造函数的规则。静态方法永远不会被实例化。它是 class 定义的一部分,它不是 class.

实例的一部分

我认为让编译器更智能(以处理 OP 提出的 Java 代码)根本不是一个明智的决定。

问:建议的编译器更改解决了哪些真正的问题

问:它可能会产生什么问题?

有时选择避免额外的复杂性(并可能产生更多问题)是更明智的选择。