Mehrere Anmerkungen desselben Typs zu einem Element?

88

Ich versuche, zwei oder mehr Anmerkungen desselben Typs auf ein einzelnes Element, in diesem Fall eine Methode, zu übertragen. Hier ist der ungefähre Code, mit dem ich arbeite:

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

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

Beim Kompilieren der obigen Informationen beschwert sich javac über eine doppelte Anmerkung:

max @ upsight: ~ / work / daybreak $ javac Dupe.java 
Dupe.java:5: doppelte Anmerkung

Ist es einfach nicht möglich, solche Anmerkungen zu wiederholen? Sind die beiden oben genannten Instanzen von @Foo pedantisch gesehen nicht unterschiedlich, da ihre Inhalte unterschiedlich sind?

Wenn dies nicht möglich ist, welche möglichen Problemumgehungen gibt es?

UPDATE: Ich wurde gebeten, meinen Anwendungsfall zu beschreiben. Hier geht.

Ich baue einen zuckerhaltigen Syntaxmechanismus auf, um POJOs Dokumentenspeichern wie MongoDB zuzuordnen. Ich möchte zulassen, dass Indizes als Anmerkungen zu den Gettern oder Setzern angegeben werden. Hier ist ein erfundenes Beispiel:

public class Employee {
    private List<Project> projects;

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

Natürlich möchte ich in der Lage sein, Instanzen von Mitarbeitern anhand verschiedener Eigenschaften von Project schnell zu finden. Ich kann entweder @Index zweimal mit unterschiedlichen expr () -Werten angeben oder den in der akzeptierten Antwort angegebenen Ansatz wählen. Obwohl Hibernate dies tut und es nicht als Hack betrachtet wird, halte ich es dennoch für sinnvoll, zumindest mehrere Annotationen desselben Typs für ein einzelnes Element zuzulassen.

Max A.
quelle
1
Es wird versucht, diese doppelte Regel zu lockern, um Ihr Programm in Java 7 zuzulassen. Können Sie bitte Ihren Anwendungsfall beschreiben?
Notnoop
Ich habe meine Frage mit einer Beschreibung bearbeitet, warum ich dies tun möchte. Vielen Dank.
Max A.
In CDI kann es nützlich sein, eine Bean für mehrere Qualifizierer bereitzustellen. Zum Beispiel habe ich gerade versucht, eine Bean an zwei Stellen wiederzuverwenden, indem ich sie als "@Produces @PackageName (" test1 ") @PackageName (" test2 ")" qualifiziert habe
Richard Corfield
Weiter: Die folgende Antwort löst dieses Problem nicht, da CDI das Composite als einen Qualifier sehen würde.
Richard Corfield

Antworten:

138

Zwei oder mehr Anmerkungen desselben Typs sind nicht zulässig. Sie könnten jedoch Folgendes tun:

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

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

Sie benötigen jedoch eine spezielle Behandlung der Foos-Annotation im Code.

Übrigens habe ich das gerade vor 2 Stunden benutzt, um das gleiche Problem zu umgehen :)

sfussenegger
quelle
2
Kannst du das auch in Groovy machen?
Excel20
5
@ Excel20 Ja. Sie müssten jedoch eckige Klammern verwenden, z @Foos([@Foo(bar="one"), @Foo(bar="two")]). Siehe groovy.codehaus.org/Annotations+with+Groovy
sfussenegger
Ein bisschen spät am Tag, aber möchten Sie auf die Ratschläge verweisen, mit denen die Liste der Foo in Foos verarbeitet werden kann? Im Moment versuche ich, das Ergebnis einer Methode zu verarbeiten, aber obwohl die Foos abgefangen werden, wird der Foo-Rat nie eingegeben
Stelios Koussouris
20

Abgesehen von den anderen genannten Möglichkeiten gibt es in Java8 noch eine weniger ausführliche Möglichkeit:

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

}

Beispiel wird standardmäßig FooContainerals Anmerkung abgerufen

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

Sowohl der obige Druck:

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

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

Jatin
quelle
Es ist darauf hinzuweisen, dass der Methoden- / Feldname in FooContainer den strengen Namen "value ()" haben muss. Andernfalls wird Foo nicht kompiliert.
Tomasz Mularczyk
..., die auch einen Annotationstyp (FooContainer) enthalten, können nicht auf mehr Typen als den wiederholbaren Annotationstyp (Foo) angewendet werden.
Tomasz Mularczyk
16

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

Ab Java8 können Sie wiederholbare Anmerkungen beschreiben:

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

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

Hinweis: valueErforderliches Feld für die Werteliste.

Jetzt können Sie Anmerkungen verwenden, die sie wiederholen, anstatt das Array zu füllen:

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

Wie von sfussenegger gesagt, ist dies nicht möglich.

Die übliche Lösung besteht darin , eine "mehrfache" Annotation zu erstellen , die ein Array der vorherigen Annotation verarbeitet. Es wird normalerweise mit dem Suffix 's' gleich benannt.

Dies wird übrigens sehr häufig in großen öffentlichen Projekten verwendet (z. B. im Ruhezustand), daher sollte es nicht als Hack betrachtet werden, sondern als richtige Lösung für diesen Bedarf.


Abhängig von Ihren Anforderungen ist es möglicherweise besser, Ihre frühere Anmerkung mehrere Werte verarbeiten zu lassen .

Beispiel:

    public @interface Foo {
      String[] bars();
    }
KLE
quelle
Ich wusste, dass das unheimlich vertraut war. Danke, dass du mein Gedächtnis aufgefrischt hast.
Max A.
Es ist jetzt mit Java 8 möglich.
Anis
4

Kombinieren Sie die anderen Antworten in der einfachsten Form ... eine Anmerkung mit einer einfachen Liste von Werten ...

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

//...

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

Wenn Sie nur 1 Parameter "bar" haben, können Sie ihn als "Wert" bezeichnen. In diesem Fall müssen Sie den Parameternamen überhaupt nicht schreiben, wenn Sie ihn wie folgt verwenden:

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

ein bisschen kürzer und ordentlicher, imho ..

mihahh
quelle
Richtiger Punkt, aber wie versucht dieser, die Frage von OP zu beantworten?
Mordechai
@MouseEvent Sie haben Recht, ich denke, es war eher eine Verbesserung der Top-Antwort von sfussenegger und gehört daher mehr in die Kommentare dort oben. Aber die Antwort ist aufgrund wiederholbarer Anmerkungen trotzdem veraltet ...
mihahh
0

In der aktuellen Java-Version konnte ich dieses Problem mit der folgenden Anmerkung beheben:

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