Java 以前 Java 版本中的运行时保留注释的兼容性

Compatibility of a Java runtime retention annotation in previous Java versions

我想在我的代码中使用 Java 8 中的 @FunctionalInterface,但我希望能够将生成的 class 文件与 Java 6 一起使用。我认为我应该将源版本设置为 1.8,将目标版本设置为 1.6

我会使用 @FunctionalInterface 只是为了文档,但我注意到它有 @Retention(RetentionPolicy.RUNTIME)。如果从来没有人使用过那个注解,会不会出问题?

如果有人在运行时遍历我的对象的注解,会不会导致缺失class异常?但如果那是真的,那么 Google Guava 怎么会 declare the JSR 305 annotation dependency 有一个 Maven <scope> of provided,这意味着在运行时缺少诸如 javax.annotation.Nonnull 之类的注释,在 Guava 中也一样,不会引起问题吗?

让我换个方式问:如果我在我的项目中使用 Google Guava 但不包含 JSR 305 依赖项,如果我在代码上使用反射,我真的会冒一些错误的风险吗?如果是这样,会出现什么错误?如果不会发生错误,那么类似地,我可以在使用 Java 版本 1.8 编译但针对版本 1.6 的源代码中使用 @FunctionalInterface 注释,而不会有任何运行时错误的风险,即使使用反射?

与任何 class 加载情况一样,如果 class 不需要(或者更确切地说,不需要加载),那么 class 在运行时不存在。运行时注释通常有同样的问题,因为如果它们在运行时保留,通常意味着有基于它们的逻辑,这意味着它们的 classes 也被加载。

但是@FunctionalInterface没有运行时逻辑,所以... Why does @FunctionalInterface have a RUNTIME retention? 显然不是出于任何特别令人信服的原因,只是它的副作用也是 @Documented 注释。

因此,如果您想确保某人(或更有可能是某些工具(我不是指 "tool",例如同事))决定枚举注释时没有潜在问题在你的 classes 中,我猜你需要在预处理时删除注释。

I think then that I should [set] the source version to 1.8, and the target version to 1.6.

实际上,不可能 为较旧的 JVM 目标版本编译 Java 较新源版本的源文件。 Oracles 和 OpenJDKs javac 将拒绝 -source 版本高于 -target 版本的编译尝试。 (但是,我找不到否认它的规范,甚至 manual 也没有提到这一点)。 javac 的交叉编译功能的唯一想法是你可以编译你的旧的,例如。即使您使用较新的 JDK 进行编译,1.6 Java 文件仍然适用于旧的 1.6 JVM。

您所描述的问题就是造成这种情况的原因。由于 Java 使用的是一种惰性依赖加载,编译器无法保证在运行时为所有依赖提供适当的 class。这也适用于标准库。

但是,有(非官方)工具可以将较新的源代码或字节码编译为较旧的字节码版本。但这不适用于标准库。如果你想使用更新的 classes,你必须自己提供它们。为此,标准库的特定部分存在一些反向端口。

具体关于你的注释问题:

如果 JVM 遇到无法检索 class 文件的注释构造(我搜索了 Java virtual machine specification SE 8). However, I found a somewhat related reference in the Java language specification SE 8:

An annotation is a marker which associates information with a program construct, but has no effect at run time.

From JLS 9.7

此声明更确切地说表明注释(存在或不存在)不应影响 JVM 的执行。因此,由于缺少注释而导致的异常(例如NoClassDefFoundError)是相当反对的。

最后,虽然 this question 的答案,我发现了更具体的说法:

An annotation that is present in the binary form may or may not be available at run time via the reflection libraries of the Java SE platform.

From JLS 9.6.4.2

Adding or removing annotations has no effect on the correct linkage of the binary representations of programs in the Java programming language.

From JLS 13.5.7

这非常清楚地表明缺少注释将不会导致错误,而是如果通过反射检查将被忽略。 而且,如果您交付带有 Java 1.8 标准库注释的 class annotated,它将(以某种方式)在例如Java 1.6 JVM,其中该注解不存在,则此规范否认生成任何错误。

这也得到了我写的以下测试的支持:(注意反射的用法)

@TestAnno
public class Test {
  public static void main(String[] args) {
    Annotation[] annos = Test.class.getAnnotations();
    for (Annotation a : annos) {
      System.out.println(a);
    }
  }
}

@Retention(RetentionPolicy.RUNTIME)
@interface TestAnno {
}

如果编译,它会产生一个 Test.class 和一个 TestAnno.class。执行程序时输出:

@TestAnno()

因为这是应用于 Test 的一个注释。现在,如果 TestAnno.class 被删除而不对 Test.class 进行任何修改(指的是 TestAnnoLTestAnno; 字节码中的序列)并且再次执行 Test ,它只是不输出任何东西。所以我的 JVM 确实 忽略了 缺少的注释并且 没有 产生任何错误或异常(使用 OpenJDK 版本测试 1.8.0_131 在 Linux).