¿Varias anotaciones del mismo tipo en un elemento?

91

Estoy intentando agregar dos o más anotaciones del mismo tipo en un solo elemento, en este caso, un método. Aquí está el código aproximado con el que estoy trabajando:

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

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

Al compilar lo anterior, javac se queja de una anotación duplicada:

max @ upsight: ~ / trabajo / amanecer $ javac Dupe.java 
Dupe.java:5: anotación duplicada

¿Simplemente no es posible repetir anotaciones como esta? Hablando pedante, ¿no son las dos instancias de @Foo anteriores diferentes debido a que sus contenidos son diferentes?

Si lo anterior no es posible, ¿cuáles son algunas posibles soluciones?

ACTUALIZACIÓN: Me han pedido que describa mi caso de uso. Aquí va.

Estoy construyendo un mecanismo de sintaxis azucarado para "mapear" POJOs a almacenes de documentos como MongoDB. Quiero permitir que los índices se especifiquen como anotaciones en los captadores o definidores. Aquí hay un ejemplo artificial:

public class Employee {
    private List<Project> projects;

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

Obviamente, quiero poder encontrar rápidamente instancias de Employee por varias propiedades de Project. Puedo especificar @Index dos veces con diferentes valores expr (), o tomar el enfoque especificado en la respuesta aceptada. Aunque Hibernate hace esto y no se considera un truco, creo que todavía tiene sentido permitir al menos tener múltiples anotaciones del mismo tipo en un solo elemento.

Max A.
fuente
1
Hay un esfuerzo para que esta regla duplicada se relaje para permitir su programa en Java 7. ¿Puede describir su caso de uso, por favor?
notnoop
He editado mi pregunta con una descripción de por qué quiero hacer esto. Gracias.
Max A.
Podría ser útil en CDI permitir que se proporcione un bean para múltiples calificadores. Por ejemplo, acabo de intentar reutilizar un bean en dos lugares calificándolo "@Produces @PackageName (" prueba1 ") @PackageName (" prueba2 ")"
Richard Corfield
Además: La respuesta a continuación no resuelve ese problema porque CDI vería el compuesto como un Calificador.
Richard Corfield
@MaxA. Cambie su aceptación de "cheque verde" por la respuesta correcta de mernst. Java 8 agregó la capacidad de repetir anotaciones.
Basil Bourque

Respuestas:

140

No se permiten dos o más anotaciones del mismo tipo. Sin embargo, podrías hacer algo como esto:

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

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

Sin embargo, necesitará un manejo dedicado de la anotación de Foos en el código.

por cierto, acabo de usar esto hace 2 horas para solucionar el mismo problema :)

sfussenegger
fuente
2
¿También puedes hacer esto en Groovy?
Excel20
5
@ Excel20 Sí. Sin embargo, tendrías que usar corchetes, por ejemplo @Foos([@Foo(bar="one"), @Foo(bar="two")]). Ver groovy.codehaus.org/Annotations+with+Groovy
sfussenegger
Un poco tarde en el día, pero ¿le importaría señalar el consejo que puede procesar la lista de Foo dentro de Foos? En este momento estoy tratando de procesar el resultado de un método, pero aunque se intercepta el Foos, el consejo de Foo nunca se ingresa
Stelios Koussouris
Actualización: a partir de Java 8, esta respuesta está desactualizada. Vea la respuesta moderna correcta de mernst. Java 8 agregó la capacidad de repetir anotaciones.
Basil Bourque
21

Aparte de las otras formas mencionadas, hay una forma menos detallada 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{

}

El ejemplo se obtiene de forma predeterminada, FooContainercomo una anotación

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

Tanto la impresión anterior:

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

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

Jatin
fuente
Vale la pena señalar que el nombre del método / campo en FooContainer tiene que ser estrictamente llamado "valor ()". De lo contrario, Foo no compilará.
Tomasz Mularczyk
... que también contiene el tipo de anotación (FooContainer) no puede ser aplicable a más tipos que el tipo de anotación repetible (Foo).
Tomasz Mularczyk
16

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

A partir de Java8, puede describir anotaciones repetibles:

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

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

Nota, value es un campo obligatorio para la lista de valores.

Ahora puede usar anotaciones repitiéndolas en lugar de llenar la matriz:

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

Como dijo sfussenegger, esto no es posible.

La solución habitual es crear una anotación "múltiple" , que maneja una matriz de la anotación anterior. Por lo general, se denomina igual, con un sufijo 's'.

Por cierto, esto es muy utilizado en grandes proyectos públicos (Hibernate por ejemplo), por lo que no debe considerarse un hack, sino una solución correcta para esta necesidad.


Dependiendo de sus necesidades, podría ser mejor permitir que su anotación anterior maneje múltiples valores .

Ejemplo:

    public @interface Foo {
      String[] bars();
    }
KLE
fuente
Sabía que esto era inquietantemente familiar. Gracias por refrescar mi memoria.
Max A.
Ahora es posible con Java 8.
Anis
4

combinando las otras respuestas en la forma más simple ... una anotación con una lista simple de valores ...

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

//...

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

Si solo tiene 1 parámetro "barra", puede nombrarlo como "valor". En este caso, no tendrá que escribir el nombre del parámetro cuando lo use de esta manera:

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

un poco más corto y ordenado, imho ..

golwig
fuente
Punto correcto, pero ¿cómo intenta esto responder a la pregunta de OP?
Mordejai
@MouseEvent tiene razón, creo que fue más una mejora de la respuesta principal de sfussenegger y, por lo tanto, pertenece más a los comentarios allí. Pero la respuesta está desactualizada de todos modos debido a las anotaciones repetibles ...
golwig
0

En la versión actual de Java, pude resolver este problema con la siguiente anotación:

@Foo({"one", "two"})
Eduardo Alexandre de Souza
fuente
1
¿cual version? jdk 8,9,10,11?
Gaurav