【问题标题】:What's the reason for java.lang.annotation.Retention?java.lang.annotation.Retention 的原因是什么?
【发布时间】:2016-09-01 01:17:09
【问题描述】:

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

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

由于don't throwClassNotFoundException 运行时没有注释(与缺少接口不同),使用RetentionPolicy.RUNTIME 似乎不会造成任何伤害。还是我错了?

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

【问题讨论】:

    标签: java annotations retention


    【解决方案1】:

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

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

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

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

    根据您的问题重新措辞:

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

    【讨论】:

    • 您的答案与最佳答案相矛盾。无论如何,问题似乎在于人们认为他们应该使用标准注释,而实际上由于保留而需要另一个注释。
    • @maaartinus:当然,已删除误导性声明。我同意,将Retention.RUNTIME 与常见的预定义注释一起使用通常没有很好的基础。对于用户定义的,这是一个不同的故事。并且其中任何一个都应该明确说明是否预期 RUNTIME 保留(以及在什么情况下)。
    【解决方案2】:

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

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

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

    由于许多注释将仅由开发工具使用,例如 存根生成器,将所有注释保留在 运行;这样做可能会增加运行时内存占用和危害 表现。但是,有些注释类型很有用 在运行时,一些在只有访问权限的工具中很有用 到类文件(不是源文件)。因此,某些注释是 由编译器存储在类文件属性 (JVMS 4.7) 中,还有一些 然后在运行时提供这些注释中的一些以供检查 通过新的反射 API。

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

    六。阅读注释

    注解消费者可分为三组:

    一个。 “Introspectors” - 查询运行时可见注释的程序 他们自己的程序元素。这些程序将同时加载带注释的 类和注释接口进入虚拟机。 (经过 运行时可见,我们的意思是注解,保留 策略 是 RUNTIME。)

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

    c。 “通用工具” - 查询程序 任意外部程序的任意注释(例如 编译器、文档生成器和类浏览器)。这些 程序既不加载带注释的类也不加载注释接口 进入虚拟机。据说此类程序“在 arm 的 长度。”

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

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

    【讨论】:

    • 我想知道为什么 Google 的 AutoValue 没有运行时保留。我个人会发现它对 Jackson 反序列化很有用。
    【解决方案3】:

    例如 javax.annotation.Generated 是用来标记生成的 代码,但它很少有用。由于 AFAIK 有更多工具正在开发 字节码比使用源代码的工具、信息 在它可以使用之前就消失了。

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

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

    例子:

    @SuppressWarnings 让你禁止警告,你还能怎么做? C# 允许您定义#PRAGMA#IF,某种条件编译。编译输出中不存储任何条件编译信息。

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

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

    类似地,许多此类注释允许您进行代码分析、编写单元测试等,并在编译前使用它做更多的工作。

    这里有更多

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

    结论

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

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

    【讨论】:

    • 同意@SuppressWarnings@Override,但@Generated 是一个示例,表明由于thisthis 工具在类文件中需要它。我们可以同意 different annotation 是必需的,也许使用标准注释确实是个坏主意。
    • @maaartinus 标准注释是什么意思?你的意思是像c中的不同语法,指令?
    • 我的意思是javax.annotation.Generated。它应该被记录为“不要使用它,除非你完全确定,你不会在类文件中需要它。而且你永远无法确定。”
    猜你喜欢
    • 2017-04-10
    • 2016-08-28
    • 2011-12-04
    • 2011-08-29
    • 1970-01-01
    • 1970-01-01
    • 2016-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多