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.

download

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.Classum 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 codero 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 este java.lang.annotation.RetentionPolicy enum declara constantes CLASS (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) e SOURCE (o compilador descarta as anotações).
  • Documented indica que as instâncias de Documented– as anotações anotadas devem ser documentadas por javadoc 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()
getAnnotations()
método que retorna uma matriz de 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; i 

After verifying that exactly one command-line argument (identifying a class file) has been specified, main() loads the class file via Class.forName(), invokes getMethods() to return an array of java.lang.reflect.Method objects identifying all public methods in the class file, and processes these methods.

Method processing begins by invoking Method’s boolean isAnnotationPresent(Class extends Annotation> annotationClass) method to determine if the annotation described by ToDo.class is present on the method. If so, Method’s T getAnnotation(Class annotationClass) method is called to obtain the annotation.

The ToDo annotations that are processed are those whose types declare a single String 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 its public methods. For example, you could modify Listing 6’s AnnDemo source code to include public in its sort() and search() method headers. You’ll also need Listing 10’s ToDo annotation type, which requires the RUNTIME 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 processar AnnDemo's ToDo Anotações:

java AnnProcDemo AnnDemo

Se 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 Doe

Tipos de anotação padrão

Juntamente com Target, Retention, Documentede InheritedJava 5 introduzido java.lang.Deprecated, java.lang.Overridee java.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 como SOURCE.

Descontinuada