Você provavelmente já se deparou com situações em que precisa associar metadados (dados que descrevem outros dados) com classes, métodos e/ou outros elementos de aplicativo. Por exemplo, sua equipe de programação pode precisar identificar classes inacabadas em um aplicativo grande. Para cada classe inacabada, os metadados provavelmente incluiriam o nome do desenvolvedor responsável por finalizar a classe e a data de conclusão esperada da classe.
Antes do Java 5, os comentários eram o único mecanismo flexível que o Java tinha a oferecer para associar metadados a elementos do aplicativo. No entanto, os comentários são uma escolha ruim. Como o compilador os ignora, os comentários não estão disponíveis em tempo de execução. E mesmo se estivessem disponíveis, o texto teria que ser analisado para obter itens de dados cruciais. Sem padronizar como os itens de dados são especificados, esses itens de dados podem ser impossíveis de analisar.
Baixe o código-fonte dos exemplos neste tutorial Java 101. Criado por Jeff Friesen para InfoWorld.
O Java 5 mudou tudo ao introduzir Anotaçõesum mecanismo padrão para associar metadados a vários elementos de aplicação. Este mecanismo consiste em quatro componentes:
- Um
@interface
mecanismo para declarar tipos de anotação. - Tipos de meta-anotação, que você pode usar para identificar os elementos do aplicativo aos quais um tipo de anotação se aplica; para identificar o tempo de vida de um anotação (uma instância de um tipo de anotação); e muito mais.
- Suporte para processamento de anotações por meio de uma extensão da API Java Reflection (a ser discutida em um artigo futuro), que você pode usar para descobrir as anotações de tempo de execução de um programa e uma ferramenta generalizada para processar anotações.
- Tipos de anotação padrão.
Explicarei como usar esses componentes à medida que avançamos neste artigo.
Declarando tipos de anotação com @interface
Você pode declarar um tipo de anotação especificando o @
símbolo imediatamente seguido por interface
palavra reservada e um identificador. Por exemplo, a Listagem 1 declara um tipo de anotação simples que você pode usar para anotar código thread-safe.
Listagem 1: ThreadSafe.java
public @interface ThreadSafe { }
Depois de declarar esse tipo de anotação, prefixe os métodos que você considera thread-safe com instâncias desse tipo, adicionando @
imediatamente seguido pelo nome do tipo para os cabeçalhos do método. A Listagem 2 oferece um exemplo simples onde o main()
método é anotado @ThreadSafe
.
Listagem 2: AnnDemo.java
(versão 1)
public class AnnDemo { @ThreadSafe public static void main(String() args) { } }
ThreadSafe
instâncias não fornecem metadados além do nome do tipo de anotação. No entanto, você pode fornecer metadados adicionando elementos a esse tipo, onde um elemento é um cabeçalho de método colocado no corpo do tipo de anotação.
Além de não terem corpos de código, os elementos estão sujeitos às seguintes restrições:
- O cabeçalho do método não pode declarar parâmetros.
- O cabeçalho do método não pode fornecer uma cláusula throws.
- O tipo de retorno do cabeçalho do método deve ser um tipo primitivo (por exemplo,
int
),java.lang.String
,java.lang.Class
um enum, um tipo de anotação ou um array de um desses tipos. Nenhum outro tipo pode ser especificado para o tipo de retorno.
Como outro exemplo, a Listagem 3 apresenta um ToDo
tipo de anotação com três elementos que identificam um trabalho de codificação específico, especificando a data em que o trabalho deve ser concluído e nomeando o codificador responsável por concluir o trabalho.
Listagem 3: ToDo.java
(versão 1)
public @interface ToDo { int id(); String finishDate(); String coder() default "n/a"; }
Observe que cada elemento não declara nenhum parâmetro ou cláusula throws, tem um tipo de retorno legal (int
ou String
), e termina com um ponto e vírgula. Além disso, o elemento final revela que um valor de retorno padrão pode ser especificado; esse valor é retornado quando uma anotação não atribui um valor ao elemento.
Listagem 4 usos ToDo
para anotar um método de classe inacabado.
Listagem 4: AnnDemo.java
(versão 2)
public class AnnDemo { public static void main(String() args) { String() cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(id = 1000, finishDate = "10/10/2019", coder = "John Doe") static void sort(Object() objects) { } }
A Listagem 4 atribui um item de metadados a cada elemento; por exemplo, 1000
é atribuído a id
. Diferente coder
o id
e finishDate
elementos devem ser especificados; caso contrário, o compilador relatará um erro. Quando coder
não é atribuído um valor, ele assume seu padrão "n/a"
valor.
Java fornece um especial String value()
elemento que pode ser usado para retornar uma lista separada por vírgulas de itens de metadados. A Listagem 5 demonstra esse elemento em uma versão refatorada de ToDo
.
Listagem 5: ToDo.java
(versão 2)
public @interface ToDo { String value(); }
Quando value()
é o único elemento de um tipo de anotação, você não precisa especificar value
e a =
operador de atribuição ao atribuir uma string a este elemento. A Listagem 6 demonstra ambas as abordagens.
Listagem 6: AnnDemo.java
(versão 3)
public class AnnDemo { public static void main(String() args) { String() cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(value = "1000,10/10/2019,John Doe") static void sort(Object() objects) { } @ToDo("1000,10/10/2019,John Doe") static boolean search(Object() objects, Object key) { return false; } }
Usando tipos de meta-anotação — o problema da flexibilidade
Você pode anotar tipos (por exemplo, classes), métodos, variáveis locais e muito mais. No entanto, essa flexibilidade pode ser problemática. Por exemplo, você pode querer restringir ToDo
apenas para métodos, mas nada impede que ele seja usado para anotar outros elementos do aplicativo, conforme demonstrado na Listagem 7.
Listagem 7: AnnDemo.java
(versão 4)
@ToDo("1000,10/10/2019,John Doe") public class AnnDemo { public static void main(String() args) { @ToDo(value = "1000,10/10/2019,John Doe") String() cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(value = "1000,10/10/2019,John Doe") static void sort(Object() objects) { } @ToDo("1000,10/10/2019,John Doe") static boolean search(Object() objects, Object key) { return false; } }
Na Listagem 7, ToDo
também é usado para anotar o AnnDemo
classe e cities
variável local. A presença dessas anotações errôneas pode confundir alguém que esteja revisando seu código, ou até mesmo suas próprias ferramentas de processamento de anotações. Para os momentos em que você precisa restringir a flexibilidade de um tipo de anotação, Java oferece o Target
tipo de anotação em seu java.lang.annotation
pacote.
Target
é um tipo de meta-anotação — um tipo de anotação cujas anotações anotam tipos de anotação, em oposição a um tipo não meta-anotação cujas anotações anotam elementos de aplicação, como classes e métodos. Ele identifica os tipos de elementos de aplicação aos quais um tipo de anotação é aplicável. Esses elementos são identificados por Target
‘s ElementValue() value()
elemento.
java.lang.annotation.ElementType
é um enum cujas constantes descrevem elementos do aplicativo. Por exemplo, CONSTRUCTOR
aplica-se a construtores e PARAMETER
aplica-se a parâmetros. Listagem 8 refatora Listagem 5’s ToDo
tipo de anotação para restringi-lo somente a métodos.
Listagem 8: ToDo.java
(versão 3)
import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface ToDo { String value(); }
Dado o refatorado ToDo
tipo de anotação, uma tentativa de compilar a Listagem 7 agora resulta na seguinte mensagem de erro:
AnnDemo.java:1: error: annotation type not applicable to this kind of declaration @ToDo("1000,10/10/2019,John Doe") ^ AnnDemo.java:6: error: annotation type not applicable to this kind of declaration @ToDo(value="1000,10/10/2019,John Doe") ^ 2 errors
Tipos adicionais de meta-anotação
O Java 5 introduziu três tipos adicionais de meta-anotações, que são encontrados em java.lang.annotation
pacote:
Retention
indica por quanto tempo as anotações com o tipo anotado devem ser retidas. O tipo associado a estejava.lang.annotation.RetentionPolicy
enum declara constantesCLASS
(o compilador registra anotações no arquivo de classe; a máquina virtual não as retém para economizar memória — política padrão),RUNTIME
(o compilador registra as anotações no arquivo de classe; a máquina virtual as retém) eSOURCE
(o compilador descarta as anotações).Documented
indica que as instâncias deDocumented
– as anotações anotadas devem ser documentadas porjavadoc
e ferramentas semelhantes.Inherited
indica que um tipo de anotação é herdado automaticamente.
O Java 8 introduziu o java.lang.annotation.Repeatable
tipo de meta-anotação. Repeatable
é usado para indicar que o tipo de anotação cuja declaração ele (meta-)anota é repetível. Em outras palavras, você pode aplicar múltiplas anotações do mesmo tipo de anotação repetível a um elemento de aplicativo, como demonstrado aqui:
@ToDo(value = "1000,10/10/2019,John Doe") @ToDo(value = "1001,10/10/2019,Kate Doe") static void sort(Object() objects) { }
Este exemplo pressupõe que ToDo
foi anotado com o Repeatable
tipo de anotação.
Processando anotações
Anotações são feitas para serem processadas; caso contrário, não há sentido em tê-las. O Java 5 estendeu a Reflection API para ajudar você a criar suas próprias ferramentas de processamento de anotações. Por exemplo, Class
declara um Annotation()
método que retorna uma matriz de
getAnnotations()java.lang.Annotation
instâncias que descrevem anotações presentes no elemento descrito pelo Class
objeto.
A Listagem 9 apresenta uma aplicação simples que carrega um arquivo de classe, interroga seus métodos para ToDo
anotações e gera os componentes de cada anotação encontrada.
Listagem 9: AnnProcDemo.java
import java.lang.reflect.Method; public class AnnProcDemo { public static void main(String() args) throws Exception { if (args.length != 1) { System.err.println("usage: java AnnProcDemo classfile"); return; } Method() methods = Class.forName(args(0)).getMethods(); for (int i = 0; iAfter verifying that exactly one command-line argument (identifying a class file) has been specified,
main()
loads the class file viaClass.forName()
, invokesgetMethods()
to return an array ofjava.lang.reflect.Method
objects identifying allpublic
methods in the class file, and processes these methods.Method processing begins by invoking
Method
’sboolean isAnnotationPresent(Class extends Annotation> annotationClass)
method to determine if the annotation described byToDo.class
is present on the method. If so,Method
’smethod is called to obtain the annotation.
T getAnnotation(Class annotationClass) The
ToDo
annotations that are processed are those whose types declare a singleString value()
element (see Listing 5). Because this element’s string-based metadata is comma-separated, it needs to be split into an array of component values. Each of the three component values is then accessed and output.Compile this source code (
javac AnnProcDemo.java
). Before you can run the application, you’ll need a suitable class file with@ToDo
annotations on itspublic
methods. For example, you could modify Listing 6’sAnnDemo
source code to includepublic
in itssort()
andsearch()
method headers. You’ll also need Listing 10’sToDo
annotation type, which requires theRUNTIME
retention policy.Listing 10:
ToDo.java
(version 4)import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ToDo { String value(); }Compilar o modificado
AnnDemo.java
e Listagem 10, e execute o seguinte comando para processarAnnDemo
'sToDo
Anotações:java AnnProcDemo AnnDemoSe tudo correr bem, você deverá observar a seguinte saída:
ID = 1000 Finish date = 10/10/2019 Coder = John Doe ID = 1000 Finish date = 10/10/2019 Coder = John DoeTipos de anotação padrão
Juntamente com
Target
,Retention
,Documented
eInherited
Java 5 introduzidojava.lang.Deprecated
,java.lang.Override
ejava.lang.SuppressWarnings
. Esses três tipos de anotação são projetados para serem usados apenas em um contexto de compilador, e é por isso que suas políticas de retenção são definidas comoSOURCE
.Descontinuada