Wiele adnotacji tego samego typu na jednym elemencie?

91

Próbuję uderzyć dwie lub więcej adnotacji tego samego typu na jednym elemencie, w tym przypadku na metodzie. Oto przybliżony kod, z którym pracuję:

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

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

Podczas kompilowania powyższego javac narzeka na zduplikowaną adnotację:

max @ upsight: ~ / work / daybreak $ javac Dupe.java 
Dupe.java:5: zduplikowana adnotacja

Czy po prostu nie można powtórzyć takich adnotacji? Mówiąc pedantycznie, czy te dwa wystąpienia @Foo powyżej nie są różne, ponieważ ich zawartość jest inna?

Jeśli powyższe nie jest możliwe, jakie są potencjalne obejścia?

UPDATE: Poproszono mnie o opisanie mojego przypadku użycia. Tutaj idzie.

Buduję mechanizm łagodnej składni, aby „mapować” POJO na magazyny dokumentów, takie jak MongoDB. Chcę zezwolić na określanie indeksów jako adnotacji na elementach pobierających lub ustawiających. Oto wymyślony przykład:

public class Employee {
    private List<Project> projects;

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

Oczywiście chcę móc szybko znaleźć wystąpienia pracownika według różnych właściwości projektu. Mogę określić @Index dwukrotnie z różnymi wartościami wyrażenia () lub zastosować podejście określone w zaakceptowanej odpowiedzi. Mimo że Hibernate to robi i nie jest to uważane za hack, myślę, że warto przynajmniej zezwolić na posiadanie wielu adnotacji tego samego typu na jednym elemencie.

Max A.
źródło
1
Podjęto próbę, aby ta zduplikowana reguła została rozluźniona, aby pozwolić programowi w Javie 7. Czy możesz opisać swój przypadek użycia?
notnoop
Zmodyfikowałem moje pytanie, podając opis, dlaczego chcę to zrobić. Dzięki.
Max A.
W CDI może być przydatne, aby umożliwić dostarczanie fasoli dla wielu kwalifikatorów. Na przykład właśnie próbowałem ponownie użyć fasoli w dwóch miejscach, kwalifikując ją „@Produces @PackageName (" test1 ") @PackageName (" test2 ")"
Richard Corfield
Dalej: Poniższa odpowiedź nie rozwiązuje tego problemu, ponieważ CDI postrzegałoby kompozyt jako jeden kwalifikator.
Richard Corfield,
@MaxA. Zmień akceptację „zielonego czeku” na poprawną odpowiedź mernst. Java 8 dodała możliwość powtarzania adnotacji.
Basil Bourque

Odpowiedzi:

140

Dwie lub więcej adnotacji tego samego typu są niedozwolone. Możesz jednak zrobić coś takiego:

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

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

Będziesz jednak potrzebować dedykowanej obsługi adnotacji Foos w kodzie.

btw, właśnie użyłem tego 2 godziny temu, aby obejść ten sam problem :)

sfussenegger
źródło
2
Czy możesz to również zrobić w Groovy?
Excel20
5
@ Excel20 Tak. Musiałbyś jednak użyć nawiasów kwadratowych, np @Foos([@Foo(bar="one"), @Foo(bar="two")]). Zobacz groovy.codehaus.org/Annotations+with+Groovy
sfussenegger
Trochę późno, ale czy chcesz wskazać porady, które mogą przetworzyć listę Foo w Foos? W tej chwili próbuję uzyskać wynik metody, ale chociaż Foos jest przechwycony, rada Foo nigdy nie jest wprowadzana
Stelios Koussouris
Aktualizacja: od wersji Java 8 ta odpowiedź jest nieaktualna. Zobacz poprawną nowoczesną odpowiedź mernst. Java 8 dodała możliwość powtarzania adnotacji.
Basil Bourque
21

Oprócz innych wymienionych sposobów, w Javie8 jest jeszcze jeden mniej rozwlekły sposób:

@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{

}

Przykład domyślnie pobiera FooContainerjako adnotacja

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

Obie powyższe wydruki:

@ com.FooContainer (wartość = [@ com.Foo (wartość = 1), @ com.Foo (wartość = 2), @ com.Foo (wartość = 3)])

@ com.FooContainer (wartość = [@ com.Foo (wartość = 1), @ com.Foo (wartość = 2), @ com.Foo (wartość = 3)])

Jatin
źródło
Warto zwrócić uwagę, że nazwa metody / pola w FooContainer musi być ściśle określona jako „wartość ()”. W przeciwnym razie Foo nie skompiluje się.
Tomasz Mularczyk
... również zawierający typ adnotacji (FooContainer) nie może mieć zastosowania do większej liczby typów niż typ powtarzalnej adnotacji (Foo).
Tomasz Mularczyk
16

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

Począwszy od Java8 możesz opisywać powtarzalne adnotacje:

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

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

Uwaga: valuepole wymagane dla listy wartości.

Teraz możesz używać adnotacji, powtarzając je zamiast wypełniania tablicy:

@Foo(bar="one")
@Foo(bar="two")
public void haha() {}
Siergiej Pikalev
źródło
12

Jak powiedział sfussenegger, nie jest to możliwe.

Typowym rozwiązaniem jest utworzenie adnotacji „wielokrotnej” , która obsługuje tablicę poprzedniej adnotacji. Zwykle ma taką samą nazwę, z przyrostkiem „s”.

Nawiasem mówiąc, jest to bardzo używane w dużych projektach publicznych (na przykład Hibernate), więc nie powinno być traktowane jako hack, a raczej poprawne rozwiązanie na tę potrzebę.


W zależności od potrzeb może być lepiej, jeśli wcześniejsza adnotacja obsługiwała wiele wartości .

Przykład:

    public @interface Foo {
      String[] bars();
    }
KLE
źródło
Wiedziałem, że to niesamowicie znajome. Dzięki za odświeżenie mojej pamięci.
Max A.
Jest to teraz możliwe z Javą 8.
Anis
4

połączenie innych odpowiedzi w najprostszą formę ... adnotacja z prostą listą wartości ...

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

//...

public @interface Foos{
    String[] value();
}
matowy1616
źródło
3

Jeśli masz tylko 1 parametr „słupek”, możesz go nazwać „wartością”. W takim przypadku nie będziesz musiał w ogóle wpisywać nazwy parametru, gdy używasz jej w ten sposób:

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

trochę krótszy i schludniejszy, imho ...

Golwig
źródło
Słuszna uwaga, ale jak to próbuje odpowiedzieć na pytanie OP?
Mordechai,
@MouseEvent masz rację, myślę, że było to bardziej ulepszenie górnej odpowiedzi od sfusseneggera, a zatem bardziej należy ją tam w komentarzach. Ale odpowiedź i tak jest nieaktualna z powodu powtarzających się adnotacji ...
golwig
0

W obecnej wersji Java udało mi się rozwiązać ten problem z następującą adnotacją:

@Foo({"one", "two"})
Eduardo Alexandre de Souza
źródło
1
która wersja? jdk 8,9,10,11?
Gaurav