3 coisas que você não sabia sobre as anotações em Java

Marcio Endo
April 30, 2023

Quem desenvolve aplicações usando a linguagem Java quase certamente conhece as anotações.

Por exemplo, considere a classe de um serviço hipotético abaixo:

@NotThreadSafe
public class MeuServico {
  ...
}

À classe foi adicionada a anotação NotThreadSafe. Ela indica que o serviço não é "thread-safe". Em outras palavras, não é seguro que threads distintas acessem o serviço simultaneamente.

É importante notar que, por si só, a anotação nada faz; apenas adiciona à classe a informação de que ela não é thread-safe.

Por outro lado, um framework, por exemplo, pode utilizar esta informação para, em tempo de execução, restringir ou sincronizar o acesso a este serviço.

Neste post falo sobre 3 coisas que talvez você não sabia sobre as anotações Java.

Bora lá.

1. Anotações são apenas modificadores

Sintaticamente falando, anotações são apenas modificadores.

Para entender considere o método toString abaixo:

@Override
public final String toString() {
  return "Anotações são apenas modificadores";
}

Saiba que, para o compilador Java, a anotação é apenas mais um modificador do método.

Então a forma seguinte também é valida:

public @Override final String toString() {
  return "Anotações são apenas modificadores";
}

Ou, se preferir, esta outra:

public final @Override String toString() {
  return "Anotações são apenas modificadores";
}

As três formas são equivalentes em termos de sintaxe.

Vale notar, no entanto, que a primeira forma é a recomendável. Principalmente quando a anotação refere-se ao método.

A terceira forma pode ser usada caso a anotação refira-se ao tipo de retorno do método.

Em resumo, prefira as formas idiomáticas da linguagem.

2. Anotações de tipo e nomes completos qualificados

No item anterior eu mencionei anotações de tipo. Não entrarei em detalhes sobre o que sejam as anotações de tipo, está fora do escopo deste post.

Basta saber que, no exemplo abaixo, as anotações Foo e Bar referem-se aos tipos e não ao campo.

public class Exemplo01 {
  public @Foo String a;

  public @Bar LocalDate b;
}

Em outras palavras, a anotação Foo adiciona informação ao tipo String e não ao atributo a. De modo similar, Bar adiciona informação ao tipo LocalDate e não ao atributo b.

Suponha que, por um motivo qualquer, você tenha que se referir aos tipos por seus nomes completos qualificados. Isto é, por algum motivo, você tenha que escrever:

public class Exemplo02 {
  public java.lang.String a;

  public java.time.LocalDate b;
}

E agora você precisa anotar os dois tipos com Foo e Bar respectivamente.

Saiba que o exemplo seguinte não compila:

// erro de compilação!
public class Exemplo02 {
  public @Foo java.lang.String a;

  public @Bar java.time.LocalDate b;
}

Para corrigir o erro de compilação é necessário fazer:

public class Exemplo02 {
  public java.lang.@Foo String a;

  public java.time.@Bar LocalDate b;
}

Ou seja, a anotação vem imediatamente antes do nome simples do tipo.

Sabia dessa?

3. Anotações de tipo e arrays

Considere o campo a da classe seguinte:

public class Exemplo01 {
  public int[][] a;
}

O seu tipo é um "array de array de ints", correto?

Temos, então, três tipos:

É possível, então, anotar cada um desses tipos individualmente.

public class Exemplo02 {
  public @C int[][] a;

  public int @A [][] b;

  public int[] @B [] c;
}

Ou anotar dois dos tipos:

public class Exemplo03 {
  public @C int @A [][] a;

  public int @A [] @B [] b;

  public @C int[] @B [] c;
}

Ou anotar os três tipos:

public class Exemplo04 {
  public @C int @A [] @B [] a;
}

Mas qual tipo exatamente está sendo anotado? Em todos os exemplos:

Até o próximo artigo

Por hoje é isto. Espero que tenha gostado.

O código para todos os exemplos pode ser encontrado no GitHub.

Se tiver comentários, dúvidas ou correções sobre este post, por favor, envie um email.