19

我很清楚 的含义RetentionPolicy并且知道它们做什么以及何时使用它们似乎有意义。对于我自己的注释,我确切地知道它们是在运行时、在类文件中还是仅用于编译时需要。但是,对于在库中定义的任何注释,恕我直言,您永远无法确定。

例如,javax.annotation.Generated用于标记生成的代码,但它很少有用。由于 AFAIK 处理字节码的工具比处理源代码的工具多,因此信息在可以使用之前就消失了。

由于运行时不存在的注释不会抛出 ClassNotFoundException(与缺少接口不同),因此使用RetentionPolicy.RUNTIME似乎不会造成任何伤害。还是我错了?

还是节省几个字节是使用不同Retentions 的原因?对我来说,这似乎导致太多问题不值得。我错过了什么?

4

3 回答 3

15

Java Annotations 的灵感出现在 2002 年之前,当时是从 Java 1.3 到 Java 1.4 的过渡。当时的高规格台式机是大约 2.5GHz 的 Pentium 4 或大约 2GHz 的 Athlon XP+,RAM 为 256 或 512MB。例如这里的评论。

问题是如何存储和检索有关代码的元数据。典型的解决方案是使用未经类型检查或直接链接到源代码的 XML 文件。其他人已经在非正式地扩展 JavaDoc(JDK 中存在源代码和扩展 API)以用于代码生成工具。解决方案 Annotations 是扩展 Javadoc 和 JLS 类规范的 hack(一个非常好的 hack)。

很明显,最初的作者担心性能问题(在 2002 年,Java 仍然相对较慢,反射非常慢,而且 Java 运行时是一个巨大的内存消耗者;有些事情永远不会改变)。这是来自对JSR-175的介绍:

由于许多注解将仅由存根生成器等开发工具使用,因此在运行时保留所有注解毫无意义;这样做可能会增加运行时内存占用并损害性能。但是,有些注释类型在运行时很有用,有些在只能访问类文件(而不是源文件)的工具中很有用。因此,编译器将某些注解存储在类文件属性 (JVMS 4.7) 中,然后通过新的反射 API 在运行时检查其中一些注解。

他们对该问题的解决方案是将问题分为三个用例:

六、阅读注释

注释消费者可以分为三组:

一个。“Introspectors”——查询其自身程序元素的运行时可见注释的程序。这些程序会将带注释的类和注释接口加载到虚拟机中。(通过运行时可见,我们指的是 保留策略 为 RUNTIME 的注释。)

湾。“特定工具” - 查询任意外部程序的已知注释类型的程序。例如,存根生成器就属于这一类。这些程序将读取带注释的类而不将它们加载到虚拟机中,但会加载注释接口。

C。“通用工具”——查询任意外部程序(如编译器、文档生成器和类浏览器)的任意注释的程序。这些程序既不会将带注释的类也不会将注释接口加载到虚拟机中。据说此类程序“在一定程度上”运作。

这允许(当时)上面定义的“特定工具”和“通用工​​具”的重要用例在不给运行时造成负担的情况下完成它们的工作;对于这些工具,注释可以是 SOURCE 或 CLASS。只有在运行时需要的注解(很明显,这被认为是少数用例)才会被加载并保留在 JVM 中。

所以,是的,保留策略是为了节省字节和运行时开销。虽然现在看起来很奇怪,但 2002 年是一个不同的世界,内存和性能是非常现实的问题。现在我们拥有 10 倍的性能和内存,您可以放心地使用 RUNTIME 保留而无需担心。

于 2016-05-13T02:10:09.877 回答
2

例如,javax.annotation.Generated 旨在标记生成的代码,但它很少有用。由于 AFAIK 处理字节码的工具比处理源代码的工具多,因此信息在可以使用之前就消失了。

看看源代码编辑器,源自 JetBrains 的 Android Studio 和许多其他 IDE,需要处理源代码,它提供了所有出色的编辑体验,只是因为编译时注释。

在编辑类(尚未编译)时,编辑器可以存储和处理注释。

例子:

@SuppressWarnings让你压制警告,你还能怎么做?#PRAGMAC #允许您定义#IF某种条件编译。没有条件编译信息存储在编译输出中。

@Override允许 Java 编译器检查基类是否有要覆盖的方法,如果你定义了一个带有错误参数的新方法,java 编译器将编译带有重载的新方法的类,但是如果存在@Overridejava 编译器会给你一个错误签名不正确匹配以覆盖该方法。

@GeneratedCode允许 IDE 在您使用“查找和替换”进行搜索时跳过要显示的类和成员,并且它允许您仅对您的代码而不是生成的代码操作 IDE。您是否看过R.*Android 中的资源,这些生成的类隐藏在 Android Studio 中,但它们确实提供了有用的代码完成列表。

同样,许多此类注释允许您进行代码分析、编写单元测试等,并在编译之前对其进行更多工作。

这里有更多

许多 ORM 框架使用编译时注释并生成有用的额外类,用于类型化查询和其他帮助类来创建表和维护模式。

结论

在上面显示的示例中,很明显所有三个和许多这样的注释都将不必要地添加这么多在运行时完全无用的字节。

#IFJava 有两种选择,一种是使用基于 c 的语言中使用的 etc 指令添加某种编译时注释。这需要新的语法和新的编辑经验等,另一个是创建Retention. Retention在不破坏语法的情况下创建是一个不错的举措。

于 2016-05-18T08:58:04.577 回答
1

注释的主要目的是为编译单元携带元数据。大多数标准注释都清楚地表达了有助于代码开发和编译的元信息(通过指定可由 IDE 或编译器验证的属性)。

注释不是为修改语言的运行时语义而设计的。因此,注解在运行时是否可用本身并不会改变执行。(当然,如果你积极地使用元信息来调整你的实现行为,那么一切皆有可能。)

如果在库 jar 中,则在某处标记注释,因为RetentionPolicy.RUNTIME显然期望从运行时访问注释(使用反射)对以后的用户有用。
如果同时注释的实现来自另一个库,那么这种期望要么是没有保证的,要么是由于该注释的特定目的可能只对某些用例有帮助。(并且为不同的保留设置构建不同的 jar 版本肯定是不合适的。)

因此,如果开发人员将注释标记为RetentionPolicy.RUNTIME有一个明确的用例,则需要在哪里进行运行时访问。注释实现是由同一个jar 还是不同的jar 提供,这可能与用例无关(例如,基于其他结构标准)。无论如何,如果您打算从这个用例中受益,您将在您的类路径中拥有这个注释库(因为您可能还需要其他组件)并且一切都很好。如果您不适用于此用例,那么您将不会受到缺少注释实现的影响。

根据您的问题重新措辞:

使用RUNTIME保留不会对程序造成任何损害,除了使(字节码)可执行文件带有死信息。仅在预期(并且认为有用)运行时使用元信息的情况下使用RUNTIME保留会增加代码质量(在可维护性和可理解性方面)。

于 2016-05-12T07:18:57.347 回答