【问题标题】:Java compiler stupidity - Constructors [duplicate]Java编译器愚蠢-构造函数[重复]
【发布时间】:2016-04-19 00:33:58
【问题描述】:

为什么 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){}
    }

try catch 是语言特性!!!

我知道编译器强制我们调用超类型构造函数,因为它应该在self对象之前初始化继承的对象(一般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年,总比没有好完全没有:'(

【问题讨论】:

  • 我们不要把 Java 编译器称为愚蠢 :)
  • 好吧,我没说它愚蠢 ;),它很聪明,但我说 Java 编译器愚蠢,这并不意味着 Java 编译器完全愚蠢 ;)
  • 这是一个问题还是一个咆哮?
  • @Laurel 一个问题,因为我想了解原因,只是因为 Java 设计者懒于实现它,或者因为还有另一件事,我不知道 :)

标签: java constructor javac super


【解决方案1】:

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

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

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

要求超类中的构造函数在子类的构造函数中的任何代码之前运行,这使事情变得更简单。

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

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

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

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

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

【讨论】:

  • 我更喜欢你的回答,首先,因为你没有重新解释我所知道的,我真的很喜欢你的回答,让事情变得更简单,我只是想知道,因为 20 年过去了Java 设计者并没有试图解决它,从 90 年代开始,他们说它的复杂任务,即使在今天 :)
  • @LaVloZMerril:我认为问题在于 Java 设计团队不认为这是一个问题,至少不是一个需要解决的问题。执行一个非常简单的规则:“首先调用超类中的构造函数”是他们解决更大问题的解决方案。我相信您是对的,在某些特殊情况下,违反该规则可能不会导致问题。但是规则就是规则,并且是强制执行的,任何特殊情况都没有例外。
【解决方案2】:

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

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

这就是为什么调用 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 对象,其继承的字段根本没有初始化。

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

【讨论】:

  • 我认为 OP 认为可以修改 Java 规范和编译器,以在调用超类的构造函数之前确定操作是否保证“安全”执行。这个答案阐明了当前要求实际上是一个更简单的设计的想法。我们应该注意不要将 simplicitystupidity 混为一谈。有时更简单更智能。 +10
  • 同意。在调用超级构造函数之前,我发现几乎不可能分析操作是否安全。如果有任何方法调用,则可能变得不安全,因为后代类可能会覆盖并可能执行一些不安全的事情。因此,任何比赋值更复杂的事情都无法在类的编译时检查或强制执行(即使类看起来不错,后代也会使其表现不佳)。
【解决方案3】:

如果构造函数体不是以显式构造函数调用开始,并且被声明的构造函数不是原始类 Object 的一部分,则构造函数体隐式以超类构造函数调用“super();”开始,调用其直接超类的构造函数,不带参数。

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

【讨论】:

  • 我已经知道了,但是好的,其他人已经说了更多有用的答案,谢谢你的时间:)
【解决方案4】:

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

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 类已经正确设置。如果我尝试在调用 super() 之前移动 System.out.println("Child") 行,编译器错误会显示此属性是严格执行的事实。

来自 [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 的词法封闭类的成员或 继承。

【讨论】:

  • 他们的问题中已经说明了您编辑之前的所有内容。他们已经意识到这种行为。至于 JLS 链接,您已经复制并粘贴了。您的回答是因为 JLS 这么说,还是您有任何其他意见要发表?
  • @Pillar 来自 OP,i know that the compiler oblige us to call the super type constructor, because it should initialise the inherited object before the self object.......I just wanted to understand why this is not implemented especially when i see Java can catch unreachable code。来自答案,This ensures that if you call any methods on the Parent class in your constructor, the Parent class has already been set up correctly.
  • @Pillar 我没有从 JLS 复制粘贴规范,我已经“引用”了它。
猜你喜欢
  • 2012-10-07
  • 2019-03-16
  • 1970-01-01
  • 2019-02-26
  • 1970-01-01
  • 2013-03-13
  • 2012-07-26
  • 2016-03-01
  • 1970-01-01
相关资源
最近更新 更多