1

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

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

  1. API - 定义框架 API - 思考 Servlet API
  2. Impl - 定义 API 实现 - 想想 Tomcat Servlet 容器
  3. 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

4

3 回答 3

1

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

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

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

于 2009-12-05T19:30:21.027 回答
0

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

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

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

于 2009-12-05T18:22:21.023 回答
0

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

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

于 2009-12-05T18:30:57.867 回答