为什么无法在Java中扩展注释?


227

我不明白为什么Java注释就像Java类一样没有继承。我认为这将非常有用。

例如:我想知道给定的注释是否为验证者。通过继承,我可以自反地浏览超类,以了解此注释是否扩展了ValidatorAnnotation。否则,我该如何实现?

那么,谁能给我这个设计决定的理由?


2
注意,顺便说一句,所有注释都扩展java.lang.annotation.Annotation,即instanceof,尽管没有显式声明这一事实,但任何注释都可以。
托马什Záluský

Answers:


166

关于未采用这种方式进行设计的原因,您可以在JSR 175设计常见问题解答中找到答案,其中说:

您为什么不支持注释子类型化(一种注释类型扩展了另一种注释类型)?

它使注释类型系统变得复杂,并使编写“特定工具”变得更加困难。

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

所以,是的,我想,原因只是KISS。无论如何,似乎这个问题(以及许多其他问题)已作为JSR 308的一部分被研究,您甚至可以找到带有Mathias Ricken已经开发的此功能的替代编译器。


67
好吧,也许我很愚蠢,但是我认为不能仅仅为了“保持简单”而扩展注释。至少,Java设计人员对类继承的看法不同:P
sinuhepop,

2
Java 8 M7似乎不支持子类注释。太遗憾了。
Ceki

2
@assylias JEP 104并不是要使注释子类化成为可能。JEP 104是用Java 8实现的,但仍不能对注释进行子类化(使一个注释扩展另一注释)。
Jesper 2015年

71

可扩展的注释将有效地增加指定和维护另一种类型系统的负担。而且这将是一个非常独特的类型系统,因此您不能简单地应用OO类型范例。

当您将多态性和继承引入注解时,请仔细考虑所有问题(例如,当子注解更改元注解规范(例如保留)时会发生什么?

对于所有用例,所有这些增加的复杂性?

您想知道给定的注释是否属于类别吗?

试试这个:

@Target(ElementType.ANNOTATION_TYPE)
public @interface Category {
    String category();
}

@Category(category="validator")
public @interface MyFooBarValidator {

}

如您所见,您可以使用提供的功能轻松对批注进行分组和分类,而不会造成不必要的麻烦。

因此,KISS是不向Java语言引入元类型类型系统的原因。

[ps编辑]

考虑到开放式元注释,我仅将String用于演示。对于您自己的给定项目,您显然可以使用类别类型的枚举并为给定批注指定多个类别(“多重继承”)。请注意,这些值完全是伪造的,仅用于演示目的:

@Target(ElementType.ANNOTATION_TYPE)
public @interface Category {
    AnnotationCategory[] category();
}
public enum AnnotationCategory {
    GENERAL,
    SEMANTICS,
    VALIDATION,
    ETC
}

@Category(category={AnnotationCategory.GENERAL, AnnotationCategory.SEMANTICS})
public @interface FooBarAnnotation {

}


不起作用将打印错误:@Target(ElementType.ANNOTATION_TYPE) @Retention(RetentionPolicy.RUNTIME) @interface C {}; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @C public @interface F {} class a{ @F public void S() {} } @Test public void blahTest() throws NoSuchMethodException { Method m = a.class.getMethod("S"); System.out.println(m.isAnnotationPresent(C.class)); }
user1615664

我们正在注释一个注释。a#S带有注释F。F本身由C注释。请尝试这样做。
alphazero

对,那是正确的。但是,有没有一种方法比阅读注释代理更干净地做到这一点?
user1615664

12

从某种意义上说,您已经有了Annotations-meta Annotations。如果使用元信息为注释添加注释,则在许多方面都等同于扩展其他接口。注释是接口,因此多态性实际上并没有发挥作用,并且由于它们本质上是静态的,因此无法进行运行时动态调度。

在您的验证器示例中,您可以仅在注释上获取带注释的类型,并查看其是否具有验证器元注释。

我可以看到的唯一用例是,继承是有帮助的,如果您希望能够通过超类型获取注释,但这会增加一堆复杂性,因为给定的方法或类型上可能有两个这样的注释,意味着必须返回一个数组,而不仅仅是一个对象。

因此,我认为最终的答案是用例是深奥的,使更多的标准用例变得复杂,使其不值得。


5

Java注释支持的设计者对Java社区造成了许多“简化”。

  1. 没有注释子类型会使许多复杂的注释变得不必要地丑陋。一个人不能简单地在批注中拥有可以容纳三件事之一的属性。一个需要具有三个单独的属性,这会使开发人员感到困惑,并且需要运行时验证以确保仅使用这三个属性之一。

  2. 每个站点只有一个给定类型的注释。这导致了完全不必要的集合注释模式。@Validation和@ Validations,@ Image和@Images等。

第二个正在Java 8中进行补救,但为时已晚。基于Java 5可能编写的许多框架,现在这些API缺陷将保留很长时间。


1
不仅如此,它还使得无法定义具有递归嵌套属性的属性-XML模式支持该属性。这使得注释严格地不如XML强大
user2833557

3

我回答这个问题可能晚了三年,但是我发现它很有趣,因为我发现自己处在同一位置。这是我的看法。您可以将注释查看为枚举。他们提供一种单向的信息-使用它或丢失它。

我遇到一种情况,想在Web应用程序中模拟GET,POST,PUT和DELETE。我非常想拥有一个名为“ HTTP_METHOD”的“超级”注释。后来我突然意识到这没关系。好吧,我不得不使用HTML表单中的隐藏字段来标识DELETE和PUT(因为POST和GET仍然可用)。

在服务器端,我寻找了一个名为“ _method”的隐藏请求参数。如果该值为PUT或DELETE,则它将覆盖关联的HTTP请求方法。话虽如此,我是否需要扩展注释以完成工作并不重要。所有注释看起来都一样,但是在服务器端它们的处理方式有所不同。

因此,在您的情况下,请放下脚步以扩展注释。将它们视为“标记”。它们“代表”某些信息,而不一定“操纵”某些信息。


2

我想到的一件事是可能具有多个注释。因此,您可以在同一位置添加验证器和更具体的注释。但我可能会误会:)


2

没想过,但是...看来您是对的,注释继承功能没有问题(至少我看不出问题所在)。

关于带有“验证器”注释的示例-然后,您可以利用“元注释”方法。即您将特定的元注释应用于整个注释接口。


我回答这个问题可能晚了三年,但我发现它很有趣,因为我发现自己处在同一位置。
2013年

1

我有同样的问题。不,你不能。我本人是“有纪律的”,以便在注释中编写属性以遵守某些标准,因此在外部获得注释时,您可以通过其属性来“嗅探”出哪种类型的od注释。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.