Várias anotações do mesmo tipo em um elemento?

88

Estou tentando inserir duas ou mais anotações do mesmo tipo em um único elemento, neste caso, um método. Este é o código aproximado com o qual estou trabalhando:

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

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

Ao compilar o acima, javac reclama sobre uma anotação duplicada:

max @ upsight: ~ / work / daybreak $ javac Dupe.java 
Dupe.java:5: anotação duplicada

Simplesmente não é possível repetir anotações como esta? Pedanticamente falando, as duas instâncias de @Foo acima não são diferentes devido ao fato de seus conteúdos serem diferentes?

Se isso não for possível, quais são algumas soluções alternativas?

ATUALIZAÇÃO: fui solicitado a descrever meu caso de uso. Aqui vai.

Estou construindo um mecanismo de sintaxe açucarado para "mapear" POJOs para armazenamentos de documentos como o MongoDB. Quero permitir que os índices sejam especificados como anotações nos getters ou setters. Aqui está um exemplo inventado:

public class Employee {
    private List<Project> projects;

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

Obviamente, eu quero ser capaz de encontrar rapidamente instâncias de Employee por várias propriedades de Project. Posso especificar @Index duas vezes com valores expr () diferentes ou adotar a abordagem especificada na resposta aceita. Mesmo que o Hibernate faça isso e não seja considerado um hack, eu acho que ainda faz sentido pelo menos permitir várias anotações do mesmo tipo em um único elemento.

Max A.
fonte
1
Há um esforço para que essa regra duplicada seja relaxada para permitir seu programa em Java 7. Você pode descrever seu caso de uso, por favor?
notnoop
Eu editei minha pergunta com uma descrição de por que quero fazer isso. Obrigado.
Max A.
Pode ser útil no CDI permitir que um bean seja fornecido para vários qualificadores. Por exemplo, acabei de tentar reutilizar um bean em dois lugares, qualificando-o "@Produces @PackageName (" test1 ") @PackageName (" test2 ")"
Richard Corfield
Além disso: A resposta abaixo não resolve esse problema porque o CDI veria o composto como um qualificador.
Richard Corfield

Respostas:

139

Duas ou mais anotações do mesmo tipo não são permitidas. No entanto, você pode fazer algo assim:

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

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

Porém, você precisará do tratamento dedicado da anotação Foos no código.

A propósito, acabei de usar isso 2 horas atrás para solucionar o mesmo problema :)

Sfussenegger
fonte
2
Você também pode fazer isso no Groovy?
Excel20
5
@ Excel20 Sim. Você teria que usar colchetes, por exemplo @Foos([@Foo(bar="one"), @Foo(bar="two")]). Veja groovy.codehaus.org/Annotations+with+Groovy
sfussenegger
Um pouco tarde, mas gostaria de apontar o conselho que pode processar a lista de Foo dentro de Foos? No momento, estou tentando processar o resultado de um método, mas embora o Foos seja interceptado, o conselho de Foo nunca é inserido
Stelios Koussouris
20

Além das outras formas mencionadas, existe uma forma menos prolixa em 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{

}

O exemplo obtém por padrão, FooContainercomo uma anotação

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

Ambos imprimem:

@ 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)])

Jatin
fonte
É importante ressaltar que o nome do método / campo no FooContainer deve ser estritamente denominado "valor ()". Caso contrário, Foo não compilará.
Tomasz Mularczyk
... também contendo tipo de anotação (FooContainer) não pode ser aplicável a mais tipos do que tipo de anotação repetível (Foo).
Tomasz Mularczyk
16

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

A partir do Java8, você pode descrever anotações repetíveis:

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

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

Nota, value é um campo obrigatório para a lista de valores.

Agora você pode usar anotações repetindo-as em vez de preencher a matriz:

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

Como disse sfussenegger, isso não é possível.

A solução usual é construir uma anotação "múltipla" , que lida com um array da anotação anterior. Normalmente é denominado o mesmo, com um sufixo 's'.

Aliás, é muito utilizado em grandes projetos públicos (Hibernate por exemplo), por isso não deve ser considerado um hack, mas sim uma solução correta para esta necessidade.


Dependendo de suas necessidades, pode ser melhor permitir que suas anotações anteriores tratem de vários valores .

Exemplo:

    public @interface Foo {
      String[] bars();
    }
KLE
fonte
Eu sabia que isso era estranhamente familiar. Obrigado por refrescar minha memória.
Max A.
Agora é possível com Java 8.
Anis
4

combinando as outras respostas da forma mais simples ... uma anotação com uma lista simples de valores ...

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

//...

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

Se você tiver apenas 1 parâmetro "bar", pode nomeá-lo como "valor". Nesse caso, você não precisará escrever o nome do parâmetro ao usá-lo desta forma:

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

um pouco mais curto e mais organizado, imho ..

golwig
fonte
Ponto correto, mas como isso tenta responder à pergunta de OP?
Mordechai
@MouseEvent você está certo, acho que foi mais uma melhoria da resposta principal do sfussenegger e, portanto, pertence mais aos comentários lá em cima. Mas a resposta está desatualizada devido a anotações repetíveis ...
golwig
0

Na versão atual do Java, consegui resolver esse problema com a seguinte anotação:

@Foo({"one", "two"})
Eduardo Alexandre de Souza
fonte
1
qual versão? jdk 8,9,10,11?
Gaurav