Introdução às Anotações Personalizadas
Para dominar anotações em Java, é essencial aprender a definir e utilizar suas próprias anotações. Antes de criar anotações customizadas, é necessário compreender os meta-annotations fornecidos pela linguagem e a sintaxe para defini-los.
Meta-Anotações
Meta-anotações são responsáveis por anotar outras anotações. Java 5.0 define quatro tipos padrão de meta-anotações que fornecem informações sobre outros tipos de annotation. Esses tipos e as classes correspondentes podem ser encontrados no pacote java.lang.annotation. Vamos analisar cada meta-anotação e seus parâmetros:
@Target
O @Target especifica o escopo dos objetos que podem ser anotados: pacotes, tipos (classes, interfaces, enumerações, tipos de annotation), membros de tipo (métodos, construtores, variáveis de membro, valores de enumeração), parâmetros de método e variáveis locais (como variáveis de loop ou parâmetros catch). O uso do target na declaração de um tipo annotation deixa mais claro qual é o seu alvo.
Função: Descrever o escopo de uso de uma anotação
Valores possíveis (ElementType):
- CONSTRUCTOR: para descrição de construtores
- FIELD: para descrição de campos
- LOCAL_VARIABLE: para descrição de variáveis locais
- METHOD: para descrição de métodos
- PACKAGE: para descrição de pacotes
- PARAMETER: para descrição de parâmetros
- TYPE: para descrição de classes, interfaces (incluindo tipos de annotation) ou declarações de enum
Exemplo de uso:
@Target(ElementType.TYPE)
public @interface Tabela {
/**
* Nome da tabela no banco de dados, padrão é o nome da classe
* @return
*/
public String nomeTabela() default "nomeClasse";
}
@Target(ElementType.FIELD)
public @interface NaoColunaBD {
}
A anotação Tabela pode ser usada em classes, interfaces (incluindo tipos de annotation) ou declarações de enum, enquanto a anotação NaoColunaBD só pode ser aplicada a variáveis de membro de classes.
@Retention
O @Retention define o tempo de retenção de uma Annotation: algumas anotações aparecem apenas no código fonte e são descartadas pelo compilador; outras são compiladas no arquivo class; as anotações compiladas no arquivo class podem ser ignoradas pela JVM, enquanto outras são lidas quando a classe é carregada (sem afetar a execução da classe, já que Annotation e classe são separados em uso). Este meta-Annotation pode ser usado para limitar o "ciclo de vida" da Annotation.
Função: Indica em que nível as informações da anotação devem ser mantidas
Valores possíveis (RetentionPolicy):
- SOURCE: válido apenas no arquivo fonte (retenção no fonte)
- CLASS: válido no arquivo class (retenção no bytecode)
- RUNTIME: válido em tempo de execução (retenção em runtime)
O tipo meta-Annotation Retention tem um único membro value, cujo valor vem do tipo enumerado java.lang.annotation.RetentionPolicy. Exemplo:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Coluna {
public String nome() default "nomeCampo";
public String nomeSetFuncao() default "setCampo";
public String nomeGetFuncao() default "getCampo";
public boolean valorPadraoBD() default false;
}
A anotação Coluna tem um valor de RetentionPolicy igual a RUNTIME, permitindo que processadores de anotação, através de reflexão, obtenham os valores dos atributos da anotação para realizar lógicas de tempo de execução.
@Documented
O @Documented indica que outros tipos de annotation devem ser tratados como API pública dos membros do programa anotado, podendo ser documentados por freramentas como javadoc. Documented é uma anotação de marcação, sem membros.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Coluna {
public String nome() default "nomeCampo";
public String nomeSetFuncao() default "setCampo";
public String nomeGetFuncao() default "getCampo";
public boolean valorPadraoBD() default false;
}
@Inherited
O @Inherited é uma anotação de marcação que indica que um tipo anotado é herdável. Se uma annotation usando @Inherited for aplicada a uma classe, essa annotation também será aplicada às subclasses da classe.
É importante notar que annotation do tipo @Inherited é herdada pelas subclasses da classe anotada. Classes não herdam annotations das interfaces que implementam, e métodos não herdam annotations dos métodos que sobrescrevem.
Quando uma annotation com @Inherited tem RetentionPolicy.RUNTIME, a API de reflexão aumenta essa herança. Ao usar java.lang.reflect para consultar uma annotation do tipo @Inherited, o código de reflexão verifica a classe e suas superclasses até encontrar a annotation desejada ou chegar ao topo da hierarquia de herança.
Exemplo de código:
@Inherited
public @interface Saudacao {
public enum CorFonte{ AZUL,VERMELHO,VERDE};
String nome();
CorFonte corFonte() default CorFonte.VERDE;
}
Definindo Anotações Personalizadas
Ao usar @interface para definir uma anotação personalizada, a mesma automaticamente herda a interface java.lang.annotation.Annotation, com detalhes tratados pelo compilador. Ao definir uma anotação, não é possível herdar outras anotações ou interfaces. O @interface é usado para declarar uma anotação, onde cada método na realidade declara um parâmetro de configuração. O nome do método é o nome do parâmetro e o tipo de retorno é o tipo do parâmetro (tipos de retorno podem ser apenas tipos primitivos, Class, String, enum). É possível usar default para declarar valores padrão para os parâmetros.
Formato de definição:
public @interface NomeAnotacao {corpo}
Tipos de dados suportados para parâmetros de anotação:
- Todos os tipos de dados primitivos (int, float, boolean, byte, double, char, long, short)
- Tipos String
- Tipos Class
- Tipos enum
- Tipos Annotation
- Arrays dos tipos acima
Regras para parâmetros em Annotation:
- Apenas modificadores de acesso public ou default podem ser usados. Por exemplo: String valor(); aqui o método é definido como default.
- Parâmetros de membro só podem usar os oito tipos de dados primitivos (byte, short, char, int, long, float, double, boolean) e tipos como String, Enum, Class, annotations e arrays desses tipos.
- Se houver apenas um parâmetro de membro, é recomendável nomeá-lo como "value", seguido de parênteses. Por exemplo, a anotação FrutoNome abaixo tem apenas um parâmetro de membro.
Exemplo Simples de Anotação Personalizada e Uso
package anotacoes;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Anotação para nome de fruta
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NomeFruta {
String value() default "";
}
package anotacoes;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Anotação para cor de fruta
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CorFruta {
/**
* Enumeração de cores
*/
public enum Cor{ AZUL,VERMELHO,VERDE};
/**
* Atributo de cor
* @return
*/
Cor corFruta() default Cor.VERDE;
}
package anotacoes;
import anotacoes.CorFruta.Cor;
public class Maçã {
@NomeFruta("Maçã")
private String nomeMaçã;
@CorFruta(corFruta=Cor.VERMELHO)
private String corMaçá;
public void setCorMaçá(String corMaçá) {
this.corMaçá = corMaçá;
}
public String getCorMaçá() {
return corMaçá;
}
public void setNomeMaçã(String nomeMaçá) {
this.nomeMaçá = nomeMaçá;
}
public String getNomeMaçá() {
return nomeMaçá;
}
public void exibirNome(){
System.out.println("O nome da fruta é: Maçã");
}
}
Valores Padrão para Elementos de Anotação
Elementos de anotação devem ter valores definidos, seja especificando um valor padrão na definição da anotação ou ao usar a anotação. Elementos de anotação de tipos não primitivos não podem ser nulos. Portanto, usar uma string vazia ou 0 como valor padrão é uma prática comum. Essa restrição torna difícil para processadores representar o estado de existência ou ausência de um elemento, pois em cada declaração de anotação, todos os elementos existem e têm valores correspondentes. Para contornar essa limitação, definioms valores especiais, como string vazia ou números negativos, para indicar a ausência de um elemento. Isso se tornou uma convenção ao definir anotações. Exemplo:
package anotacoes;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Anotação para fornecedor de fruta
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FornecedorFruta {
/**
* ID do fornecedor
* @return
*/
public int id() default -1;
/**
* Nome do fornecedor
* @return
*/
public String nome() default "";
/**
* Endereço do fornecedor
* @return
*/
public String endereco() default "";
}
Após definir anotações e aplicá-las às classes e atributos relevantes, o próximo passo é implementar o processamento dessas informações. Sem um mecanismo de processamento correspondente, as anotações não têm valor prático. A chave para fazer as anotações realmente funcionarem está nos métodos de processamento de anotação, que abordaremos em nosso próximo estudo sobre obtenção e processamento de informações de anotação!