Plusieurs annotations du même type sur un élément?

88

J'essaie de gifler deux ou plusieurs annotations du même type sur un seul élément, dans ce cas, une méthode. Voici le code approximatif avec lequel je travaille:

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

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

Lors de la compilation de ce qui précède, javac se plaint d'une annotation en double:

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

N'est-il tout simplement pas possible de répéter des annotations comme celle-ci? Pédantiquement parlant, les deux instances de @Foo ci-dessus ne sont-elles pas différentes en raison de leur contenu différent?

Si ce qui précède n'est pas possible, quelles sont les solutions de contournement potentielles?

MISE À JOUR: on m'a demandé de décrire mon cas d'utilisation. Voici.

Je construis un mécanisme de syntaxe sucré pour "mapper" les POJO aux magasins de documents tels que MongoDB. Je veux permettre aux index d'être spécifiés comme annotations sur les getters ou les setters. Voici un exemple artificiel:

public class Employee {
    private List<Project> projects;

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

Évidemment, je veux pouvoir trouver rapidement des instances de Employee par différentes propriétés de Project. Je peux soit spécifier @Index deux fois avec des valeurs expr () différentes, soit adopter l'approche spécifiée dans la réponse acceptée. Même si Hibernate fait cela et que ce n'est pas considéré comme un hack, je pense qu'il est toujours logique de permettre au moins d'avoir plusieurs annotations du même type sur un seul élément.

Max A.
la source
1
Il y a un effort pour assouplir cette règle de duplication afin d'autoriser votre programme en Java 7. Pouvez-vous décrire votre cas d'utilisation s'il vous plaît?
notnoop
J'ai édité ma question avec une description des raisons pour lesquelles je veux faire cela. Merci.
Max A.
Il pourrait être pratique dans CDI de permettre à un bean d'être fourni pour plusieurs qualificatifs. Par exemple, je viens d'essayer de réutiliser un bean à deux endroits en le qualifiant "@Produces @PackageName (" test1 ") @PackageName (" test2 ")"
Richard Corfield
De plus: la réponse ci-dessous ne résout pas ce problème car CDI verrait le composite comme un seul qualificatif.
Richard Corfield

Réponses:

139

Deux annotations ou plus du même type ne sont pas autorisées. Cependant, vous pouvez faire quelque chose comme ceci:

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

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

Vous aurez cependant besoin d'une gestion dédiée de l'annotation Foos dans le code.

btw, je viens de l'utiliser il y a 2 heures pour contourner le même problème :)

sfussenegger
la source
2
Pouvez-vous aussi faire cela dans Groovy?
Excel20
5
@ Excel20 Oui. Vous devrez cependant utiliser des crochets, par exemple @Foos([@Foo(bar="one"), @Foo(bar="two")]). Voir groovy.codehaus.org/Annotations+with+Groovy
sfussenegger
Un peu tard dans la journée mais attention à pointer sur les conseils qui peuvent traiter la liste des Foo inside Foos? En ce moment j'essaye de deviner le résultat d'une méthode mais bien que le Foos soit intercepté le conseil Foo n'est jamais entré
Stelios Koussouris
20

Outre les autres moyens mentionnés, il existe une autre manière moins verbeuse en 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{

}

Exemple par défaut obtient, FooContainersous forme d'annotation

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

Les deux imprimés ci-dessus:

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

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

Jatin
la source
Il convient de souligner que le nom de la méthode / du champ dans FooContainer doit être strictement nommé "value ()". Sinon, Foo ne compilera pas.
Tomasz Mularczyk
... contenant également le type d'annotation (FooContainer) ne peut pas être applicable à plus de types que le type d'annotation répétable (Foo).
Tomasz Mularczyk
16

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

À partir de Java8, vous pouvez décrire des annotations répétables:

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

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

Remarque, value est un champ obligatoire pour la liste de valeurs.

Vous pouvez maintenant utiliser des annotations en les répétant au lieu de remplir le tableau:

@Foo(bar="one")
@Foo(bar="two")
public void haha() {}
Sergei Pikalev
la source
12

Comme l'a dit sfussenegger, ce n'est pas possible.

La solution habituelle est de créer une annotation "multiple" , qui gère un tableau de l'annotation précédente. Il porte généralement le même nom, avec un suffixe «s».

D'ailleurs, cela est très utilisé dans les grands projets publics (Hibernate par exemple), donc cela ne doit pas être considéré comme un hack, mais plutôt comme une solution correcte à ce besoin.


En fonction de vos besoins, il peut être préférable d' autoriser votre annotation précédente à gérer plusieurs valeurs .

Exemple:

    public @interface Foo {
      String[] bars();
    }
KLE
la source
Je savais que c'était étrangement familier. Merci d'avoir rafraîchi ma mémoire.
Max A.
C'est désormais possible avec Java 8.
Anis
4

combinant les autres réponses dans la forme la plus simple ... une annotation avec une simple liste de valeurs ...

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

//...

public @interface Foos{
    String[] value();
}
matt1616
la source
3

Si vous n'avez qu'un seul paramètre «barre», vous pouvez le nommer «valeur». Dans ce cas, vous n'aurez pas du tout à écrire le nom du paramètre lorsque vous l'utilisez comme ceci:

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

un peu plus court et plus net, à mon humble avis ..

Golwig
la source
Bon point, mais comment cela tente-t-il de répondre à la question d'OP?
Mordechai
@MouseEvent vous avez raison, je pense que c'était plus une amélioration de la meilleure réponse de sfussenegger et appartient donc davantage aux commentaires là-haut. Mais la réponse est de toute façon obsolète en raison d'annotations répétables ...
golwig
0

Dans la version actuelle de Java, j'ai pu résoudre ce problème avec l'annotation suivante:

@Foo({"one", "two"})
Eduardo Alexandre de Souza
la source
1
quelle version? jdk 8,9,10,11?
Gaurav