一个元素上同一类型的多个注释?

我试图在一个元素上添加两个或多个相同类型的注释,在本例中是一个方法。下面是我正在处理的大致代码:

public class Dupe {
public @interface Foo {
String bar();
}


@Foo(bar="one")
@Foo(bar="two")
public void haha() {}
}

在编译上面的代码时,javac 会抱怨一个重复的注释:

max@upsight:~/work/daybreak$ javac Dupe.java
Dupe.java:5: duplicate annotation

这样重复注释是不可能的吗?教条地说,上面的@Foo 的两个实例不是因为它们的内容不同而不同吗?

如果上述情况不可能发生,那么有哪些可能的解决办法?

更新: 我被要求描述我的用例。

我正在构建一个语法含糖的机制来“映射”POJO 到文档存储,比如 MongoDB。我希望将索引指定为 getter 或 setter 上的注释。这里有一个人为的例子:

public class Employee {
private List<Project> projects;


@Index(expr = "project.client_id")
@Index(expr = "project.start_date")
public List<Project> getProjects() { return projects; }
}

显然,我希望能够通过 Project 的各种属性快速查找 Employee 实例。我可以使用不同的 expr ()值指定@Index 两次,或者采用接受答案中指定的方法。尽管 Hibernate 可以做到这一点,而且它不被认为是一种黑客行为,但我认为至少允许在一个元素上有多个相同类型的注释是有意义的。

90792 次浏览

Note: This answer is partially outdated since Java 8 introduced the @Repeatable annotation (see answer by @mernst). The need for a @Foos container annotation and dedicated handling still remain though.

Two or more annotations of same type aren't allowed. However, you could do something like this:

public @interface Foos {
Foo[] value();
}


// pre Java 8
@Foos({@Foo(bar="one"), @Foo(bar="two")})
public void haha() {}


// post Java 8 with @Repeatable(Foos.class) on @Foo
@Foo(bar="one") @Foo(bar="two")
public void haha() {}

You'll need dedicated handling of Foos annotation in code though.

As said by sfussenegger, this isn't possible.

The usual solution is to build an "multiple" annotation, that handles an array of the previous annotation. It is typically named the same, with an 's' suffix.

By the way, this is very used in big public projects (Hibernate for example), so it shouldn't be considered as a hack, but rather a correct solution for this need.


Depending on your needs, it could be better to allow your earlier annotation to handle multiple values.

Example:

    public @interface Foo {
String[] bars();
}

If you have only 1 parameter "bar" you can name it as "value". In this case you wont have to write the parameter name at all when you use it like this:

@Foos({@Foo("one"), @Foo("two")})
public void haha() {}

a bit shorter and neater, imho..

Repeating annotations in Java 8

In Java 8 (released in March 2014), it is possible to write repeated/duplicate annotations.

See tutorial, Repeating Annotations.

See specification, JEP 120: Repeating Annotations.

combining the other answers into the simplest form ... an annotation with a simple list of values ...

@Foos({"one","two"})
private String awk;


//...


public @interface Foos{
String[] value();
}

http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html

Starting from Java8 you can describe repeatable annotations:

@Repeatable(FooValues.class)
public @interface Foo {
String bar();
}


public @interface FooValues {
Foo[] value();
}

Note, value is required field for values list.

Now you can use annotations repeating them instead of filling the array:

@Foo(bar="one")
@Foo(bar="two")
public void haha() {}

Apart from the other ways mentioned, there is one more less verbose way in Java8:

@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Foo {
String value();


}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FooContainer {
Foo[] value();
}


@Foo("1") @Foo("2") @Foo("3")
class Example{


}

Example by default gets, FooContainer as an Annotation

    Arrays.stream(Example.class.getDeclaredAnnotations()).forEach(System.out::println);
System.out.println(Example.class.getAnnotation(FooContainer.class));

Both the above print:

@com.FooContainer(value=[@com.Foo(value=1), @com.Foo(value=2), @com.Foo(value=3)])

@com.FooContainer(value=[@com.Foo(value=1), @com.Foo(value=2), @com.Foo(value=3)])

In the current version of Java, I was able to resolve this issue with the following annotation:

@Foo({"one", "two"})