【问题标题】:Calling a private super class constructor implicitly from the subclass results in random behavior从子类隐式调用私有超类构造函数会导致随机行为
【发布时间】:2015-04-05 11:27:28
【问题描述】:
package pack1;
public class Outer{
    private Outer(){}
}

package pack2;
import pack1.*;
public class ExtendedOuter extends Outer{
        //compiler adds default no-args constructor, something like this
        /*
        public ExtendedOuter(){
             super(){}
        }
        */
}

now from a third class

import pack1.Outer;
import pack2.ExtendedOuter;
class InnerClasses{
    public static void main(String[] args){
        ExtendedOuter o=new ExtendedOuter();
    }
}

但是,嘿, Outer 的构造函数是私有的,所以你会认为这段代码不能编译,但它编译得很好。
只有在运行时我们才知道当它抛出 IllegalAccessError 时我们搞砸了 但这只是有时会发生! 有时您确实会收到错误 Outer() is private ,有时您不会收到错误 如何解释这种随机行为?

但是在编译时会警告私有方法调用。那么为什么不涉及隐式添加的构造函数呢? PS:如果您在ExtendedOuter 类中显式添加构造函数ExtendedOuter(){},编译器会抛出错误Outer() is private in Outer class

编辑-----------

我很抱歉,但这没有任何意义。它确实不可预测,并且结果因一个编译而异
惊喜
好的,如果这不起作用,请尝试将外部构造函数的修饰符从 private 切换到 public 好的,现在它可以工作并编译了 现在再次将修饰符切换为private,再次编译!它不应该有,不是吗?但是运行它你会遇到IllegalAccessError

现在一段时间后,它甚至无法编译 private 显示典型消息 Outer() is not public in Outer class
伟大的 。现在将Outer()构造函数的修饰符再次切换为public,编译!现在再次切换到private,编译(而之前它没有编译)

自己尝试一下,然后告诉我 但别忘了用 -cp 编译 javac

【问题讨论】:

  • 它无法编译。你不能扩展一个没有可访问构造函数的类。
  • @Sarabjeet,你用的是什么编译器?
  • 您可能没有重新编译所有类。当然,如果在Outer有公共构造函数的情况下先编译子类,然后将Outer类改为私有构造函数,只重新编译Outer,子类不会被修改,运行时会抛出异常。确保每次更改后重新编译所有类。
  • 好吧,我认为这不是 JB Nizet 的问题,因为我刚刚在 ExtendedOuter 中插入了一个新方法并保存了它,但没有编译 ExtendedOuter 并且当我访问来自 mainclass 的方法,它工作正常。我认为所有链接到主类的类都会自动重新编译
  • @Sarabjeet 请接受您的问题的答案,而不是未完成。我当然不是要你选择我的答案,但它会帮助其他人知道你最终接受的答案。如果尚未发布答案,请自行制作并接受您的答案。

标签: java inheritance constructor


【解决方案1】:

这个问题真的是两个问题。不幸的是,由于问题的性质,这些答案有些投机。以下是我认为您想要回答的问题:

私有构造函数的编译机制是什么 从自动生成的默认构造函数中引用?

如果你创建一个类private的构造函数,这个类就不能被子类化。 private 构造函数与 final 关键字一样有效地防止子类化;所有子类都将通过super() 强制尝试调用无法访问的构造函数。因为ExtendedOuter 类仅隐式引用Outer 类的private 构造函数(通过生成的构造函数代码),编译器似乎没有意识到您正在尝试调用无法访问的构造函数。

您的编译器似乎没有考虑默认构造函数生成的副作用。但是,在重新编译您的类时,编译器可能会采用使用先前编译中可用的类字节码的捷径。这种字节码重用可以解释后续重新编译如何正确识别不可访问的构造函数。您的编译器无法通过代码分析确定什么,它可以通过字节码分析确定(当然,假设没有访问您的确切编译器/机器/等,我无法确定)。

为什么我执行提供的类结构会生成 IllegalAccessError 不确定,尽管没有更改 编译代码?

如果您查看 Outer 类,构造函数不包含会阻止将其作为优化移除的分支或方法调用。 ExtendedOuter 生成的默认构造函数也可能会被 JIT 编译器乐观删除。要完全回答这个问题,需要确定 JIT 编译器可能确定的有关方法和构造函数调用的内容。

JIT 编译器是一个复杂的话题,但空的构造函数调用不太可能被 JIT 编译器透明地内联甚至完全替换为空操作。如果 JIT 编译器进行代码替换和优化,那么很容易导致程序多次执行中的代码路径不一致。 JVM 及其 JIT 编译器不能保证产生确定性的输出。

【讨论】:

  • 谢谢 Emily,但现在如果我在认证考试中被问到,这样的程序的输出应该是什么,我可以在两个选项之间选择什么:编译失败或在运行时抛出异常
  • @Sarabjeet 我想说最正确的答案是在运行时生成Error(不是Exception)。我相信这可能是暂停问题的一个例子,在这种情况下,编译器不可能以 100% 的准确度正确评估代码错误。本质上,我认为可以做出的最明确的声明是,如果构造函数被执行,那么会生成一个Error。我强烈建议您参考有关此主题的 Java 语言规范,因为我不是 100% 有信心,也不想误导您并损害您的分数。
  • 我在 JLS 中找不到任何地方。你能帮我找到吗
  • 适用于构造函数访问的规则可以在JLS §6.6.1中找到。
【解决方案2】:

如果您尝试创建具有私有构造函数的类的对象,编译器会抛出错误,因为在编译期间检测到对象创建是不可能的。在您的情况下,必须调用构造函数以验证对象创建。当创建ExtendedOuter 类的对象时,编译器会检查其可用的默认构造函数。在编译期间不调用任何方法。在运行时,类Outer 的构造函数被调用,结果为IllegalAccessError。 我仍然不清楚编译器的随机行为。您在编译之前是否进行了任何代码更改?您能否详细说明您为测试代码而执行的具体步骤?

【讨论】:

  • 这没有回答问题。
猜你喜欢
  • 2015-09-21
  • 2021-09-19
  • 2016-05-08
  • 1970-01-01
  • 1970-01-01
  • 2011-02-16
  • 2012-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多