【问题标题】:Why javac requires an interfaces of referenced classes when ECJ does not?为什么 javac 需要引用类的接口,而 ECJ 不需要?
【发布时间】:2017-07-11 09:08:45
【问题描述】:

编译Client 时,它使用接口I 的某些实现(例如O),I 的类文件也必须存在于类路径中。奇怪的是,这只是javac 的情况,因为Eclipse 编译器(ECJ)不需要I 进行编译。

是什么让 JDK 需要 超类型用于编译,而 ECJ 编译得很好?

这不是默认方法,正如in the bug report 所评论的那样,compatibility guide 也同意:

当针对实现在另一个类文件中定义的接口的另一个类编译一个类时,此类文件(定义接口的位置)必须在编译期间 javac 使用的类路径中可用。这是 JDK 8 的新要求 - 不这样做将导致编译错误。


更新

  • 类似问题:Java 8 interface/class loader changes?
  • 不管I.doit()default还是普通抽象方法,行为都是一样的
  • I.doit() 是否被O 覆盖当然很重要;如果没有被覆盖,那么 ECJ 也会到达 I 以获取 doit() 的定义

接口(@98​​7654337@):

package a;
public interface I {
    default void doit() {
        System.out.println("In I");
    }
}

实施 (impl/b/O.java):

package b;
public class O implements a.I {
    public void doit() {
        System.out.println("In O");
    }
}

客户 (client/c/Client.java):

package c;
import b.O;
public class Client {
    public void test() {
        O o = new O();
        o.doit();
    }
    public static void main(String[] args) {
        new Client().test();
    }
}

Makefile:

# bug report:
#   Javac requires interface on classpath when using impl
#   https://bugs.openjdk.java.net/browse/JDK-8055048
#
# compatibility guide:
#   http://www.oracle.com/technetwork/java/javase/8-compatibility-guide-2156366.html
#   (Synopsis: Interfaces need to be present when compiling against their implementations)
# 
# ECJ downloaded from:
#   http://central.maven.org/maven2/org/eclipse/jdt/core/compiler/ecj/4.6.1/ecj-4.6.1.jar

ifeq (${V}, ecj)
JC := java -jar ecj-4.6.1.jar -8
else
JC := javac -source 1.8 -target 1.8 -implicit:none
endif

rebuild: clean lib client

lib: api/a/I.class impl/b/O.class

client: lib client/c/Client.class

clean:
    rm -f api/a/I.class impl/b/O.class client/c/Client.class

%.class: %.java
    ${JC} ${OPT} $<

impl/b/O.class: OPT = -cp api
client/c/Client.class: OPT = -cp impl

日志:

$ make V=ecj rebuild                                                                                                                                                                                               
rm -f api/a/I.class impl/b/O.class client/c/Client.class
java -jar ecj-4.6.1.jar -8  api/a/I.java
java -jar ecj-4.6.1.jar -8 -cp api impl/b/O.java
java -jar ecj-4.6.1.jar -8 -cp impl client/c/Client.java

$ make rebuild
rm -f api/a/I.class impl/b/O.class client/c/Client.class
javac -source 1.8 -target 1.8 -implicit:none  api/a/I.java
javac -source 1.8 -target 1.8 -implicit:none -cp api impl/b/O.java
javac -source 1.8 -target 1.8 -implicit:none -cp impl client/c/Client.java
client/c/Client.java:8: error: cannot access I
                o.doit();
                 ^
  class file for a.I not found
1 error
make: *** [client/c/Client.class] Error 1

【问题讨论】:

  • 你确定在 ECJ 的情况下它不在类路径上吗?
  • 是的,查看日志。这是一个与更复杂的案例分开的测试案例,其中 Eclipse 可以很好地编译而没有传递依赖,而 javac(从 Gradle CI 脚本运行)抱怨。
  • 我想看看当你从 O 类中删除 doit() 时 ECJ 会做什么,但其他所有内容都保持不变......
  • 实际上可能是 ECJ 错误。
  • 本兼容性指南描述了javac实际行为,它不是关于编译器必须如何工作的规范。不是javachas been noticed already的变化行为。

标签: java java-8 javac ecj


【解决方案1】:

似乎对Compatibility Guide for JDK 8的目的有误解。

这不是关于编译器或环境应该如何行为的规范,它是关于 JDK确实如何行为的文档,以发现潜在的兼容性问题。这并不意味着另一个编译器必须表现出完全相同的行为。

之所以提到该特定行为,是因为 javac 将其行为从 JDK 7 更改为 JDK 8,这可能会导致兼容性问题。

正如here 解释的那样,正式过程被描述为搜索所有可能适用的成员方法以进行方法调用,但它并不是说在可以保证程序的正确性时不允许使用捷径.

所以that bug report 已关闭,因为新行为符合规范,不一定是因为替代行为会违反规范。

【讨论】:

  • 嗯,寻找“潜在适用的方法”似乎是个好点。但是,我在 JLS/JVM 中很难找到一个重要的案例,即找到一个需要执行 javac 而不是 ECJ 的不情愿并将其报告为 ECJ 中的错误的案例
  • 我认为这两种行为都符合规范。我认为,像正式规范一样实现它的主要原因,没有任何捷径,是为了让编译器的代码更简单,因为整个过程很复杂。如果 ECJ 开发人员认为,他们仍然可以维护支持该快捷方式的代码,他们可能会继续这样做。在任何一种情况下,我们都不应该将此类快捷方式的存在视为一种有保证的行为。
【解决方案2】:

如果O 不覆盖doit() 怎么办?

那么Client 必须仍然能够调用doit(),因为它是I 合同的一部分,但是O 的类文件中缺少此信息。

您可能会问“为什么不在O 的类文件中包含默认方法定义?”。这将破坏首先引入默认方法的意图:使用 Java-8 之前的编译器编译的类在 Java 8 中仍然可以工作,并且新方法的接口应该可用。

【讨论】:

  • 正如我在问题中所写的,这不仅仅是默认方法的情况。去掉默认方法,ECJ和javac的不同行为还是会出现。此外,由于doit() 的信息存在于O 中,因此无需签入I
猜你喜欢
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 2018-03-18
  • 2016-12-29
  • 2018-07-30
  • 2011-06-18
  • 2021-04-13
  • 1970-01-01
相关资源
最近更新 更多