重载的package-private方法导致编译失败 - 这是一个JLS奇怪或javac错误吗?

时间:2009-12-05 18:05:28

标签: java overloading javac api-design

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

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

  1. API - 定义框架API - 想想Servlet API
  2. Impl - 定义API实现 - 想想Tomcat Servlet容器
  3. App - 我写的应用程序
  4. 以下是每个模块中的类:

    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导出的,例如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

3 个答案:

答案 0 :(得分:1)

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

编辑1:澄清:这与您认为的包可见方法无关。问题是编译需要类型MessagePrinterInternal,但是类路径上没有它。当javac无法访问引用的类时,你不能指望它能够编译源代码。

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

答案 1 :(得分:0)

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

接下来就是,AFAIK,这是一个Java奇怪的东西(因为它不会做你想要的)。如果你摆脱了公共方法并将软件包私有化,你就会得到同样的东西。

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

答案 2 :(得分:0)

我想你总是可以争辩说javac可以更聪明,但它必须停在某个地方。它不是人类,人类可以永远比编译器更聪明,你总能找到对人类完全有意义的例子但却让人傻眼的编译器。

我不知道这个问题的具体规格,我怀疑javac作者在这里犯了什么错误。但谁在乎?为什么不将所有依赖项放在类路径中,即使它们中的一些是肤浅的?这样做可以让我们的生活更轻松。