重载的包私有方法导致编译失败 - 这是 JLS 怪异还是 javac 错误?

Posted

技术标签:

【中文标题】重载的包私有方法导致编译失败 - 这是 JLS 怪异还是 javac 错误?【英文标题】:Overloaded package-private method causes compilation failure - Is this a JLS oddity or javac bug? 【发布时间】:2010-12-23 14:26:37 【问题描述】:

我遇到了 JLS 的一个奇怪之处,或者一个 JavaC 错误(不确定是哪一个)。请阅读以下内容并提供解释,并酌情引用 JLS 段落或 Sun Bug ID。

假设我有一个人为的项目,其中包含三个“模块”中的代码 -

    API - 定义框架 API - 考虑 Servlet API Impl - 定义 API 实现 - 想想 Tomcat Servlet 容器 App - 我写的应用程序

这是每个模块中的类:

API - MessagePrinter.java

package api;

public class MessagePrinter 

    public void print(String message) 
        System.out.println("MESSAGE: " + message);
    

API - MessageHolder.java(是的,它引用了一个“impl”类 - 稍后会详细介绍)

​​>
package api;

import impl.MessagePrinterInternal;

public class MessageHolder 

    private final String message;

    public MessageHolder(String message) 
         this.message = message;
    

    public void print(MessagePrinter printer) 
        printer.print(message);
    

    /**
     * NOTE: Package-Private visibility.
     */ 
    void print(MessagePrinterInternal printer) 
        printer.print(message);    
    


Impl - MessagePrinterInternal.java - 这个类依赖于一个 API 类。顾名思义,它是为我的小框架中其他地方的“内部”使用而设计的。

package impl;

import api.MessagePrinter;

/**
 * An "internal" class, not meant to be added to your
 * application classpath. Think the Tomcat Servlet API implementation classes.
 */
public class MessagePrinterInternal extends MessagePrinter 

    public void print(String message) 
        System.out.println("INTERNAL: " + message);
    

最后,App 模块中唯一的类...MyApp.java

import api.MessageHolder;
import api.MessagePrinter;

public class MyApp 

    public static void main(String[] args) 
        MessageHolder holder = new MessageHolder("Hope this compiles");
        holder.print(new MessagePrinter());
    


所以,现在我尝试编译我的小应用程序 MyApp.java。假设我的 API jar 是通过一个 jar 导出的,比如 api.jar,并且作为一个好公民,我只在我的类路径中引用了那个 jar,而不是 impl.jar 中的 Impl 类。

现在,显然我的框架设计存在缺陷,即 API 类不应依赖于“内部”实现类。然而,令人惊讶的是 MyApp.java 根本没有编译。

javac -cp api.jar src\MyApp.java
src\MyApp.java:11: cannot access impl.MessagePrinterInternal class file for impl.MessagePrinterInternal not found

    holder.print(new MessagePrinter());
                 ^
      1 error

问题在于,由于方法重载,编译器正在尝试解析要使用的 print() 版本。但是,编译错误有点出乎意料,因为其中一种方法是包私有的,因此对 MyApp 不可见。

那么,这是 javac 的错误,还是 JLS 的一些奇怪之处?

编译器:Sun javac 1.6.0_14

【问题讨论】:

“由于运算符重载”应该是“由于方法重载” 【参考方案1】:

首先,我希望 api 包中的东西是接口而不是类(基于名称)。一旦你这样做了,问题就会消失,因为你不能在接口中访问包。

接下来的事情是,AFAIK,这是一个 Java 奇怪的东西(因为它没有做你想做的事)。如果您摆脱公共方法并将包设为私有,您将得到同样的结果。

将 api 包中的所有内容更改为接口将解决您的问题并在代码中提供更清晰的分离。

【讨论】:

即使您更改此代码以使用接口,您也可以通过声明接口包私有来重现相同的行为。例如,MessagePrinterInternal 可以是 API 包中的接口。事实上,我基于这个问题的生产代码正是这样做的。 如果接口是包,只有包外的任何东西都看不到。如果您只对接口进行编码(将所有变量和参数声明为接口),那么应该没有问题。不管有没有一个名为 API 的包包含接口、枚举、工厂和异常以外的东西,对我来说都是没有意义的。 很抱歉,您所说的另一件事是基于您所说的 - API 不应包含私有 thi fs 包,因为它应该是公开消费的东西。【参考方案2】:

我想你总是会争辩说 javac 可以更聪明一点,但它必须在某个地方停下来。它不是人类,人类总是比编译器更聪明,你总能找到对人类非常有意义但编译器傻眼的例子。

我不知道这件事的确切规范,我怀疑 javac 作者在这里犯了任何错误。但谁在乎?为什么不将所有依赖项都放在类路径中,即使其中一些是肤浅的?始终如一地这样做让我们的生活更轻松。

【讨论】:

有些类不应该在编译类路径中,因为它们只打算在运行时使用。例如,Sun Java 附带的 Sun 类。 例如?大多数 sun 类都在 rt.jar 中,编译时在类路径中。 直接使用 com.sun 类被普遍认为是最坏的做法:java.sun.com/products/jdk/faq/faq-sun-packages.html【参考方案3】:

JLS 或 javac 没有任何问题。当然这不会编译,因为你的类 MessageHolder 引用 MessagePrinterInternal 如果我理解你的解释正确的话,它不在编译类路径中。您必须将此引用分解到实现中,例如使用 API 中的接口。

编辑 1:澄清:这与您似乎认为的 package-visible 方法无关。问题是编译需要MessagePrinterInternal 类型,但类路径中没有它。当 javac 无法访问引用的类时,你不能期望它编译源代码。

编辑 2:我再次重新阅读代码,这似乎正在发生:编译 MyApp 时,它尝试加载类 MessageHolder。 MessageHolder 类引用 MessagePrinterInternal,因此它也尝试加载它并失败。我不确定 JLS 中是否指定了它,它也可能取决于 JVM。根据我使用 Sun JVM 的经验,加载类时至少需要所有静态引用的类可用;这包括字段的类型、方法签名中的任何内容、扩展类和实现的接口。您可能会争辩说这是违反直觉的,但我会回应说,一般来说,您对缺少此类信息的类几乎没有做任何事情:您不能实例化对象,不能使用元数据(Class 对象)等。那些背景知识,我会说你看到的行为是预期的。

【讨论】:

是的,但是使用 MessagePrinterInternal 的方法是包私有的。 MyApp.java 类将永远无法访问它。所以这是违反直觉的。 这对你来说可能违反直觉,但我认为他是正确的。试试看吧。 他的回答只是重申了我的问题。当然,源代码需要更改才能编译。我的问题:它在 JLS 中的哪个位置将此编译失败定义为预期行为?

以上是关于重载的包私有方法导致编译失败 - 这是 JLS 怪异还是 javac 错误?的主要内容,如果未能解决你的问题,请参考以下文章

JLS 的哪一部分说匿名类不能有公共/受保护/私有成员类

Typescript 解析私有范围包的类型

生产环境中的包编译。这会导致任何问题吗?

Vue.js / webpack:当热重载编译它们时,如何清除旧的包 main-*.js 文件?

将自行添加的包编译进Tomcat

编译期多态和Sfinae