Java编译器无法识别静态内部类

6

这是一个相当复杂的错误,请耐心听我解释。

在编译一些Java代码时,我遇到了一个奇怪的错误。编译器无法识别一个静态内部类。假设我正在处理一个名为MyClass的类。我需要使用的静态内部类具有x.y.z.Parent.DesiredClass的完全限定名称。这个内部类是通过其完全限定名称显式导入的。父类也是通过其完全限定名称导入的。现在存在另一个包含DesiredClass类的包(不同于前面提到的FQN),该类也在类路径下,但没有被显式导入。

在继续之前,我应该明确指出,无法更改这些类的名称。

现在,当我在我的代码中引用Parent.DesiredClass时,我使用Parent.DesiredClass完全限定名称来避免任何可能的歧义。但是当我编译时,当我尝试实例化Parent.DesiredClass时,我会得到一个错误。我的代码片段:

x.y.z.Parent.DesiredClass dc;
dc = new x.y.z.Parent.DesiredClass();

这会产生以下编译时错误:
MyClass.java:123: an enclosing instance that contains x.y.z.Parent.DesiredClass is required
   dc = new x.y.z.Parent.DesiredClass();
           ^

请注意,被链接的类是使用不同的Java编译器编译的:
  • MyClass 旨在使用Sun Java 1.4.2_18进行编译
  • x.y.z.Parent.DesiredClass 和另一个 DesiredClass 是使用Microsoft Java编译的。
很遗憾,这些类无法重新编译为更现代的Java版本。
此外,在尝试使用Sun Java 1.4.2_18进行编译时,以下异常会在编译器中发生:
An exception has occurred in the compiler (1.4.2_18). Please file a bug at the Java Developer Connection (http://java.sun.com/cgi-bin/bugreport.cgi)  after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report.  Thank you.
java.lang.NullPointerException
        at com.sun.tools.javac.v8.code.Type.isSubTypes(Type.java:557)
        at com.sun.tools.javac.v8.comp.Resolve.instantiate(Resolve.java:221)
        at com.sun.tools.javac.v8.comp.Resolve.selectBest(Resolve.java:317)
        at com.sun.tools.javac.v8.comp.Resolve.findMethod(Resolve.java:414)
        ...
        at com.sun.tools.javac.v8.comp.Attr.attribClass(Attr.java:1332)
        at com.sun.tools.javac.v8.JavaCompiler.compile(JavaCompiler.java:355)
        at com.sun.tools.javac.v8.Main.compile(Main.java:569)
        at com.sun.tools.javac.Main.compile(Main.java:36)
        at com.sun.tools.javac.Main.main(Main.java:27)
Error encountered running Java Compiler

Aborting compilation.

如果我使用更新版本的Java(1.5或更高版本)编译,编译器异常不会发生,但仍会发生上述错误。请问有人能解释一下这个错误吗?为什么编译器没有将静态内部类识别为静态,即使使用了它的FQN也是如此?非常感谢您的帮助。
编辑: 经过进一步调查,我发现问题是由我需要在路径中拥有一个库中的单行代码触发的。我可以访问此库的源代码,但不将其编译为我的项目的一部分。这行代码(假设在类TheirClass中)正是我想要做的事情:即实例化x.y.z.DesiredClass。如果我删除TheirClass中的此行代码(但不删除MyClass中的此行代码),则不会收到编译错误。
因此,总结如下:以下内容不起作用:
MyClass.java:
x.y.z.Parent.DesiredClass dc;
dc = new x.y.z.Parent.DesiredClass();

TheirClass.java:

x.y.z.Parent.DesiredClass dc;
dc = new x.y.z.Parent.DesiredClass();

以下内容是可行的:
MyClass.java:
x.y.z.Parent.DesiredClass dc;
dc = new x.y.z.Parent.DesiredClass();

TheirClass.java:

//x.y.z.Parent.DesiredClass dc;
//dc = new x.y.z.Parent.DesiredClass();

我将尝试遵循@Richard的建议,尽可能地减少代码量,以便我可以发布一个样例。

2
你能提供 DesiredClass 的代码吗?你遇到的错误通常是在没有封闭类的实例的情况下尝试实例化非静态内部类时看到的。 - selig
“静态内部类”这个词本身就是一个矛盾。它要么是内部类,要么是静态的。请参阅JLS。 - user207421
尊敬的@EJP,"inner"和"static"这两个术语并不是互斥的。内部类不一定需要是静态的。我认为静态类确实需要嵌套在另一个类中。但它可以访问外部类的成员,包括(静态)私有成员。因此,“静态内部”这个术语是有效的,以区分它与非静态内部类。 - phantom-99w
阅读评论后,这是我的想法。按定义,内部类不能是静态的。例如,Java语言规范在这里:http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.1.3说明:“内部类是一个嵌套类,它没有明确或隐式地声明为静态的。”因此,虽然在实践中,人们可能知道你说“静态内部类”时通常指什么,但更正确的做法可能是说“静态嵌套类”,否则纯粹主义者会反对你的用法。 - gcbound
3个回答

2

在仍在忙于 @Richard 和 @Stephen 建议的低级调查时,我对这个问题有了几个想法。

我首先尝试扩展 DesiredClass,并使用一个更加清晰的名称,然后看看如果我使用扩展类会不会起作用。但是,它没有起作用,并引发了相同的错误。

然后,我尝试使用 Class.forName() 实例化 DesiredClass

DesiredClass dc;
try {
    dc = (DesiredClass) Class.forName(classname).newInstance();
}
catch (Exception e) {

}

这行得通!

虽然有些混乱,但这让我能够继续我的工作。我很想知道错误的根本原因(也许是编译器使用的类加载器存在问题,特别是考虑到已经用不同“品牌”的编译器编译了一些类?),但花更多时间来尝试找出这个问题的成本效益不值得。

感谢所有做出贡献的人。


2
错误信息 "an enclosing instance that contains x.y.z.Parent.DesiredClass is required" 表示编译器认为 DesiredClass 不是 static
建议您获取 Parent$DesiredClass.class 文件,并使用 javap 检查编译后的类属性。这将告诉您,您正在编译的类是否真的是一个静态内部类。

谢谢您的回答。我查看了 javap(详细)输出,没有明显的错误跳出来。 - phantom-99w

1
我建议尝试将此错误精简到最少的类中。
第一步:
拿出您的自定义代码项目,并查看是否可以将有问题的类缩减为一个失败构建的单行代码。然后尝试从您的自定义项目中删除所有其他类,仍然无法构建。
在此单个类中进行一些重命名方法等操作,以隐藏生产代码的“秘密”信息,希望您会有一个可以发布在此论坛上的源文件。
第二步:
对正在使用的产品代码执行相同操作(不是JAR文件,而是其他类文件)。
然后对于JAR文件内容。在每个点(费力且乏味),确认您仍然会收到相同的错误。
第三步:
希望可以使用三个类文件来复制此问题,其中没有包含任何生产名称/代码等。 将它们发布供我们查看-我认为这是一个有趣的问题。
ymmv

谢谢Richard,我会去做并尽快回复您。 - phantom-99w

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接