Java Anotações
Uma anotação é uma etiqueta que começa com @ e fica grudada numa classe, num método ou num campo: @Override, @Deprecated, @Test. Ela não muda o que o código faz; ela informa algo sobre o código para quem vai lê-lo: o compilador, uma ferramenta ou um framework.
- O que é uma anotação e onde ela pode ficar.
- As quatro que você mais vai ver:
@Override,@FunctionalInterface,@Deprecatede@SuppressWarnings, com os erros e avisos reais dojavac. - Para que servem as anotações dos frameworks (
@Testdo JUnit,@GetMappingdo Spring…). - Como criar uma anotação sua, com elementos e valores padrão, e lê-la com reflexão.
Você já usou @Override na aula de sobrescrita e talvez @FunctionalInterface em interfaces funcionais. Agora vamos entender o que esse @ é. Os exemplos rodaram no JDK 25 da máquina da Ana, na pasta ~/java.
A ideia antes do código
Pense nas etiquetas que a Ana cola nas caixas da loja: "FRÁGIL", "ESTE LADO PARA CIMA", "PROMOÇÃO ATÉ SEXTA". A etiqueta não muda o que tem dentro da caixa. Mas quem pega a caixa lê a etiqueta e age de acordo: o entregador carrega com cuidado; o caixa aplica o desconto.
Uma anotação é a mesma coisa no código. @Override é uma etiqueta que diz ao compilador "este método substitui um da classe mãe; confira para mim". @Deprecated diz "este método está aposentado; avise quem usar". Quem lê as etiquetas pode ser:
- o compilador (
javac), na hora de compilar; - uma ferramenta, como o Javadoc ou o IDE;
- o próprio programa ou um framework, enquanto roda, usando reflexão.
Como se escreve
| Forma | Exemplo | Quando |
|---|---|---|
| Só o nome | @Override | A anotação não precisa de informação extra |
| Um valor | @SuppressWarnings("deprecation") | Tem um elemento principal (chamado value) |
| Pares nome = valor | @Deprecated(since = "2.0", forRemoval = true) | Vários elementos |
| Uma lista | @SuppressWarnings({"deprecation", "unchecked"}) | O elemento aceita vários valores |
A anotação fica logo antes do que ela etiqueta, normalmente na linha de cima. Pode etiquetar classes, métodos, campos, parâmetros e variáveis locais.
1. @Override: "confira que estou sobrescrevendo"
A classe Produto quer mostrar um texto bonito no println. Para isso, ela precisa sobrescrever o método toString() que herda de Object. A Ana digitou o nome com um s minúsculo:
class Produto {
String nome;
double preco;
Produto(String nome, double preco) {
this.nome = nome;
this.preco = preco;
}
public String tostring() {
return nome + " (R$ " + preco + ")";
}
}
public class Main {
public static void main(String[] args) {
Produto p = new Produto("Café", 12.5);
System.out.println(p);
}
}
$ javac Main.java && java Main Produto@1dbd16a6
Compilou sem reclamar e mostrou um texto estranho: o toString() padrão do Object (o nome da classe, @ e um número que muda de uma execução para outra). Para o Java, tostring é só mais um método, com outro nome, que ninguém chama. O erro passou despercebido.
Agora com a etiqueta:
@Override
public String tostring() {
return nome + " (R$ " + preco + ")";
}
$ javac Main.java
Main.java:10: error: method does not override or implement a method from a supertype
@Override
^
1 error
"O método não sobrescreve nem implementa um método de um supertipo (classe mãe ou interface)." O compilador leu a etiqueta, foi conferir e achou o problema antes de o programa rodar. Corrigindo para toString:
$ javac Main.java && java Main Café (R$ 12.5)
- @
- Marca o início de uma anotação.
- Override
- "Sobrescrever", em inglês. É uma anotação que vem com o Java (
java.lang.Override), por isso não precisa deimport.
Regra de ouro: sempre que sobrescrever um método, escreva @Override. Ela custa uma linha e pega erros de digitação, de parâmetros e de nomes que mudaram na classe mãe.
Missão: acrescente @Override acima do método tostring e rode: veja o javac acusar o problema. Depois corrija o nome do método para que o programa mostre Café (R$ 12.5).
class Produto {
String nome;
double preco;
Produto(String nome, double preco) {
this.nome = nome;
this.preco = preco;
}
public String tostring() {
return nome + " (R$ " + preco + ")";
}
}
public class Main {
public static void main(String[] args) {
Produto p = new Produto("Café", 12.5);
System.out.println(p);
}
}
class Produto {
String nome;
double preco;
Produto(String nome, double preco) {
this.nome = nome;
this.preco = preco;
}
@Override
public String toString() {
return nome + " (R$ " + preco + ")";
}
}
public class Main {
public static void main(String[] args) {
Produto p = new Produto("Café", 12.5);
System.out.println(p);
}
}
2. @FunctionalInterface: "esta interface é para lambdas"
Uma interface funcional tem exatamente um método abstrato, e por isso pode receber uma lambda. A etiqueta @FunctionalInterface pede ao compilador que garanta isso. Se alguém acrescentar um segundo método abstrato:
@FunctionalInterface
interface Desconto {
double aplicar(double preco);
double reverter(double preco);
}
$ javac Main.java
Main.java:1: error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
Desconto is not a functional interface
multiple non-overriding abstract methods found in interface Desconto
1 error
"Anotação @FunctionalInterface inesperada: Desconto não é uma interface funcional; foram encontrados vários métodos abstratos." Sem a etiqueta, a interface compilaria, e o erro só apareceria em outro arquivo, no dia em que alguém tentasse usar uma lambda com ela. Com um método só, tudo funciona:
@FunctionalInterface
interface Desconto {
double aplicar(double preco);
}
public class Main {
public static void main(String[] args) {
Desconto metade = preco -> preco / 2;
Desconto dezReais = preco -> preco - 10;
System.out.println(metade.aplicar(80.0));
System.out.println(dezReais.aplicar(80.0));
}
}
$ java Main.java 40.0 70.0
3. @Deprecated: "aposentado, não use"
Deprecated quer dizer "desaprovado", "obsoleto". Quando um método antigo foi substituído por um melhor, mas ainda não pode ser apagado (porque tem gente usando), ele ganha essa etiqueta. O código continua funcionando, mas o compilador avisa quem o usa.
class Loja {
@Deprecated
static double precoAntigo(double valor) {
return valor + 10;
}
static double preco(double valor) {
return valor + 8;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Loja.precoAntigo(100));
}
}
$ javac Main.java
Note: Main.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
$ javac -Xlint:deprecation Main.java
Main.java:14: warning: [deprecation] precoAntigo(double) in Loja has been deprecated
System.out.println(Loja.precoAntigo(100));
^
1 warning
$ java Main
110.0
Repare nos três momentos:
- O
javaccomum mostra só duas notas ("Main.javausa ou sobrescreve uma API obsoleta"; "recompile com-Xlint:deprecationpara ver os detalhes"). O arquivo.classfoi gerado: nota não é erro. - Com a opção
-Xlint:deprecation, aparece o aviso (warning) completo, com linha e setinha: "precoAntigo(double)emLojahas been deprecated" (foi marcado como obsoleto). - O programa roda normalmente.
Marcado para remoção
A anotação aceita dois elementos: since ("desde qual versão") e forRemoval ("vai ser removido"). Com forRemoval = true, o aviso fica mais forte e aparece mesmo sem o -Xlint:
class Loja {
@Deprecated(since = "2.0", forRemoval = true)
static double precoAntigo(double valor) {
return valor + 10;
}
}
$ javac Main.java && java Main
Main.java:10: warning: [removal] precoAntigo(double) in Loja has been deprecated and marked for removal
System.out.println(Loja.precoAntigo(100));
^
1 warning
110.0
"…foi marcado como obsoleto e para remoção." Ou seja: numa versão futura, esse método vai sumir, e o seu código vai parar de compilar. Hora de trocar para o método novo.
A própria biblioteca do Java usa essa etiqueta. O construtor new Integer(5) é obsoleto desde o Java 9 (o certo é Integer.valueOf(5) ou só 5, veja wrappers):
$ javac -Xlint:deprecation Main.java
Main.java:3: warning: [deprecation] Integer(int) in Integer has been deprecated
Integer quantidade = new Integer(5);
^
1 warning
Junto com @Deprecated, escreva no comentário de documentação o que usar no lugar: /** @deprecated use {@link #preco(double)} */. A etiqueta avisa; o comentário explica. Veja Javadoc.
4. @SuppressWarnings: "eu sei, pode ficar quieto"
Suppress warnings = "suprimir avisos". Às vezes você usa algo obsoleto de propósito (por exemplo, enquanto migra um sistema antigo) e não quer que o aviso esconda outros mais importantes. A etiqueta diz qual categoria de aviso calar, o nome que aparece entre colchetes na mensagem ([deprecation], [removal], [unchecked]…):
public class Main {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
Integer quantidade = new Integer(5);
System.out.println(quantidade);
}
}
$ javac Main.java && java Main 5
Sem notas e sem avisos. Use com moderação e no menor pedaço possível (um método, não a classe inteira): um aviso calado é um aviso que ninguém mais vai ver.
| Valor | Cala o aviso de… |
|---|---|
"deprecation" | Uso de algo @Deprecated |
"removal" | Uso de algo @Deprecated(forRemoval = true) |
"unchecked" | Conversões de generics que o compilador não consegue conferir |
"rawtypes" | Uso de List sem <…> |
5. Anotações de frameworks
Até aqui, quem lia as etiquetas era o compilador. Mas as anotações mais famosas do mundo Java são lidas por frameworks (bibliotecas grandes que "chamam o seu código") enquanto o programa roda. Você não precisa saber usá-las agora; só reconhecer o padrão quando encontrar:
| Anotação | De onde | O que diz |
|---|---|---|
@Test | JUnit | "Este método é um teste: rode-o e confira." Você vai usar na aula de JUnit. |
@BeforeEach | JUnit | "Rode isto antes de cada teste." |
@Component, @Service | Spring | "Crie um objeto desta classe e entregue a quem precisar." |
@GetMapping("/produtos") | Spring | "Quando alguém acessar o endereço /produtos no site, chame este método." |
@Entity, @Id | Jakarta Persistence (JPA) | "Esta classe vira uma tabela do banco de dados; este campo é a chave." |
Como um framework "lê" uma etiqueta? Com reflexão: ele examina as suas classes enquanto o programa roda, procura os métodos com @Test e os chama. Na próxima seção você faz isso em miniatura.
6. Criando uma anotação
Você pode criar suas próprias etiquetas. A Ana quer marcar os métodos que são tarefas da loja, com o nome do responsável e uma prioridade. Esta seção é uma apresentação: criar anotações é mais comum em quem escreve bibliotecas do que no dia a dia.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Tarefa {
String responsavel();
int prioridade() default 1;
}
public class Main {
@Tarefa(responsavel = "Ana", prioridade = 3)
static void conferirEstoque() { }
@Tarefa(responsavel = "Bruno")
static void limparVitrine() { }
static void almocar() { }
public static void main(String[] args) {
Method[] metodos = Main.class.getDeclaredMethods();
Arrays.sort(metodos, Comparator.comparing(Method::getName));
for (Method m : metodos) {
Tarefa t = m.getAnnotation(Tarefa.class);
if (t != null) {
System.out.println(m.getName() + " -> " + t.responsavel() + ", prioridade " + t.prioridade());
}
}
}
}
$ javac Main.java && java Main conferirEstoque -> Ana, prioridade 3 limparVitrine -> Bruno, prioridade 1
| Linha | O que faz |
|---|---|
@interface Tarefa | Declara uma anotação (repare no @ antes de interface). O nome é o da etiqueta: @Tarefa. |
String responsavel(); | Um elemento: parece um método, mas é um "campo" da etiqueta. Sem valor padrão, é obrigatório. |
int prioridade() default 1; | Elemento com valor padrão: quem não informar recebe 1 (foi o caso do Bruno). |
@Retention(RetentionPolicy.RUNTIME) | "Retenção": a etiqueta deve continuar existindo enquanto o programa roda. Sem isso, ela some depois da compilação, e a reflexão não a enxerga. |
@Target(ElementType.METHOD) | "Alvo": esta etiqueta só pode ser colada em métodos. |
getDeclaredMethods() | Reflexão: a lista dos métodos da classe Main. Ela vem em ordem qualquer, por isso o Arrays.sort por nome. |
m.getAnnotation(Tarefa.class) | Lê a etiqueta @Tarefa do método, ou null se ele não tiver (o almocar e o próprio main). |
Repare que as anotações @Retention e @Target são anotações sobre a anotação (os nomes técnicos são "meta-anotações"). É exatamente assim que o JUnit define o @Test.
Qual usar?
| Situação | Anotação |
|---|---|
| Você sobrescreveu um método da classe mãe ou de uma interface | @Override (sempre) |
| Criou uma interface para receber lambdas | @FunctionalInterface |
| Um método seu foi substituído por outro melhor | @Deprecated (+ since, e forRemoval = true se vai sumir) |
| Usa algo obsoleto de propósito e quer calar o aviso | @SuppressWarnings("deprecation"), no menor trecho possível |
| Escreve testes | @Test do JUnit |
| Quer marcar código para uma ferramenta sua ler | Uma anotação própria com @Retention(RUNTIME) |
Quando dá errado
method does not override or implement a method from a supertype
Um método com @Override não corresponde a nenhum método da classe mãe ou das interfaces: nome digitado errado (tostring), parâmetros diferentes ou classe que não herda de quem você pensa. Conserto: confira nome, parâmetros e o extends/implements. Não apague o @Override para "fazer compilar": ele está mostrando um erro de verdade.
Unexpected @FunctionalInterface annotation
A interface tem mais de um método abstrato (seção 2). Conserto: deixe só um abstrato; os outros podem virar default (com corpo), ou tire a anotação se a interface não é mesmo para lambdas.
Faltou um elemento obrigatório
@Tarefa(prioridade = 2)
static void conferirEstoque() { }
$ javac Main.java
Main.java:14: error: annotation @Tarefa is missing a default value for the element 'responsavel'
@Tarefa(prioridade = 2)
^
1 error
O que quer dizer: "falta um valor para o elemento responsavel", que não tem default. Conserto: @Tarefa(responsavel = "Ana", prioridade = 2).
Etiqueta no lugar errado
@Tarefa(responsavel = "Ana")
public class Main {
$ javac Main.java Main.java:13: error: annotation interface not applicable to this kind of declaration @Tarefa(responsavel = "Ana") ^ 1 error
O que quer dizer: "a anotação não se aplica a este tipo de declaração". O @Target(ElementType.METHOD) só permite métodos, e ela foi colada numa classe. Conserto: mova-a para um método, ou acrescente ElementType.TYPE ao @Target: @Target({ElementType.METHOD, ElementType.TYPE}).
Avisos de deprecated não são erros
As linhas Note: … uses or overrides a deprecated API. e warning: [deprecation] … não impedem a compilação: o .class é gerado e o programa roda. Mas leve-as a sério, principalmente [removal]: numa próxima versão, aquele código deixa de compilar.
Referência
| Anotação | Onde | Quem lê | Efeito |
|---|---|---|---|
@Override | Método | javac | Erro se não sobrescrever nada |
@FunctionalInterface | Interface | javac | Erro se não tiver exatamente 1 método abstrato |
@Deprecated | Qualquer declaração | javac, Javadoc, IDE | Nota/aviso para quem usa |
@Deprecated(since = "…", forRemoval = true) | Qualquer declaração | javac | Aviso [removal] sempre |
@SuppressWarnings("…") | Classe, método, variável | javac | Cala uma categoria de aviso |
@SafeVarargs | Método com … | javac | Cala um aviso de generics com varargs |
@Retention, @Target | Declaração de anotação | javac, JVM | Por quanto tempo existe; onde pode ser usada |
Exercícios
1. Complete com a anotação que pede ao compilador para conferir a sobrescrita:
public String toString() { return "Café"; }
2. Complete para marcar o método como obsoleto e com remoção prevista:
3. Complete para calar os avisos de uso de API obsoleta:
4. Sem @Override, um método tostring() (com s minúsculo) numa classe…
5. Para que um framework encontre a sua anotação enquanto o programa roda, ela precisa de…
Missão final: etiquete a interface Desconto com @FunctionalInterface e crie as duas lambdas que faltam: metade (divide o preço por 2) e dezReais (tira 10). O programa deve mostrar 40.0 e 70.0. Depois, para ver a etiqueta trabalhando, tente acrescentar um segundo método abstrato à interface.
interface Desconto {
double aplicar(double preco);
}
public class Main {
public static void main(String[] args) {
// crie as lambdas metade e dezReais aqui
System.out.println(metade.aplicar(80.0));
System.out.println(dezReais.aplicar(80.0));
}
}
@FunctionalInterface
interface Desconto {
double aplicar(double preco);
}
public class Main {
public static void main(String[] args) {
Desconto metade = preco -> preco / 2;
Desconto dezReais = preco -> preco - 10;
System.out.println(metade.aplicar(80.0));
System.out.println(dezReais.aplicar(80.0));
}
}
Resumo
- Uma anotação (
@Nome) é uma etiqueta: não muda o que o código faz, mas informa o compilador, ferramentas ou frameworks. @Overridefaz ojavacconferir a sobrescrita (method does not override or implement a method from a supertype). Use sempre.@FunctionalInterfacegarante um único método abstrato.@Deprecatedgera nota/avisohas been deprecated; comforRemoval = true, o aviso[removal]aparece sempre.@SuppressWarnings("categoria")cala avisos, no menor trecho possível.- Frameworks (JUnit, Spring, JPA) leem anotações com reflexão. Você cria as suas com
@interface,@Retentione@Target.
Próxima aula: Reflexão, o jeito de um programa examinar a si mesmo.