【问题标题】:Compatibility of a Java runtime retention annotation in previous Java versions以前 Java 版本中 Java 运行时保留注释的兼容性
【发布时间】:2017-08-09 17:18:30
【问题描述】:

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

我将使用@FunctionalInterface 仅用于文档,但我注意到它有@Retention(RetentionPolicy.RUNTIME)。如果没有人使用该注释,会不会引起问题?

如果有人在运行时对我的对象的注解进行迭代,是否会导致缺少类异常?但是,如果这是真的,那么 Google Guava 怎么能declare the JSR 305 annotation dependency 提供一个 Maven <scope>,这意味着在 Guava 中,运行时也缺少诸如 javax.annotation.Nonnull 之类的注释,而不会造成问题?

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

【问题讨论】:

  • 您阅读了您的链接吗?它明确指出您需要添加 JSR305 实现,因此 javax.annotation.Nonnull 不会 在运行时丢失。所以是的,如果在运行时FunctionalInterface 丢失并且需要加载,您将获得NoClassDefFoundError
  • 其实我之前确实读过它,但显然我读得不够仔细。似乎是说,如果您实际上不使用 JSR 305 注释,则不会遇到问题,但是如果您在 Guava 上使用反射,如果您没有包含,将会遇到问题您自己项目中的依赖项。 (奇怪的是,我已经在我自己的项目中包含了 JSR 305 注释和 provided<scope> 并且从未遇到问题......但我没有尝试过反思。)也许你可以添加一个单独的答案来澄清我的用例,以便我可以将其标记为已接受。无论如何,谢谢。
  • 好的,我再次阅读了原始链接,这绝不是直截了当的。首先,您所说的“[i]t 明确指出您需要添加 JSR305 实现,因此 javax.annotation.Nonnull 在运行时丢失”的链接并不完全正确;问题和答案是关于为什么有人在 compile 时突然不再获得 JSR 305 支持。在阅读 stackoverflow.com/a/3567969/421049 之后,我不清楚如果将它们声明为 provided,通过反射迭代 JSR 305 注释是否会导致问题。我很困惑。
  • 我想如果你只迭代它们,它们不会引起问题,根据你的链接。但是我想 JSR305 注释通常不只是迭代,你有特定的硬依赖来检查不同的注释是否存在并基于它执行逻辑。
  • @kayaman 在这里你“假设”并且在你的回答中你“猜测”。我不是刻意刻薄,但你的答案似乎不是很确定……我想我问这个问题是为了看看有人知道肯定,而不是假设或猜测。跨度>

标签: java maven annotations runtime


【解决方案1】:

我想我应该[设置]源版本为1.8,目标版本为1.6

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

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

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

特别是关于您的注释问题:

如果 JVM 遇到无法检索类文件的带注释构造(我搜索了 Java virtual machine specification SE 8),我无法找到任何可靠的规范来说明应该/可能发生的情况。但是,我在Java language specification SE 8 中找到了一些相关的参考:

注解是将信息与程序构造相关联的标记,但在运行时不起作用。

来自JLS 9.7

该语句表明注解(存在或不存在)不应影响 JVM 的执行。因此,由于缺少注释而导致的异常(例如NoClassDefFoundError)是相当反对的。

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

以二进制形式存在的注解在运行时可能通过 Java SE 平台的反射库可用,也可能不可用。

来自JLS 9.6.4.2

添加或删除注解不会影响 Java 编程语言中程序的二进制表示的正确链接。

来自JLS 13.5.7

这很清楚地表明缺少注释将不会导致错误,而是如果通过反射检查将被忽略。 如果你提供一个带有 Java 1.8 标准库注释的类 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 进行任何修改(指的是TestAnno 字节码中带有LTestAnno; 序列)并且再次执行Test,它只是不输出任何东西。所以我的 JVM 确实 忽略 缺少的注释,并且 not 会产生任何错误或异常(在 Linux 上使用 OpenJDK 版本 1.8.0_131 测试) .

【讨论】:

【解决方案2】:

与任何类加载情况一样,如果不需要(或者更确切地说,不需要加载)该类,则该类在运行时是否不存在也没关系。运行时注解通常也有同样的问题,因为如果它们在运行时被保留,通常意味着有基于它们的逻辑,意味着它们的类也被加载了。

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

因此,如果您想确保在某人(或更可能是某些工具(我不是指“工具”,例如同事))决定枚举您的课程中的注释时不会出现潜在问题,我想您需要在预处理时删除注释。

【讨论】:

  • 您“猜测”我需要删除注释 --- 但您没有告诉我原因。而且您还没有确切地告诉我会发生什么错误,以及在什么情况下。答案似乎只是假设,这就是为什么我要在问题中添加赏金以获得更明确的答案。
猜你喜欢
  • 1970-01-01
  • 2012-11-30
  • 2011-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-01
  • 2020-10-16
相关资源
最近更新 更多