Visão geral das anotações internas do Java

Visão geral das anotações internas do Java

1. Overview

Neste artigo, vamos falar sobre um recurso central da linguagem Java - as anotações padrão disponíveis no JDK.

2. O que é uma anotação

Simplificando, as anotações sãoJava types that are preceded by an “@” symbol.

O Java tem anotações desde o lançamento da versão 1.5. Desde então, eles moldaram a maneira como projetamos nossos aplicativos.

Spring e Hibernate são ótimos exemplos de estruturas que dependem muito de anotações para permitir várias técnicas de design.

Basicamente,an annotation assigns extra metadata to the source code it’s bound to. Ao adicionar uma anotação a um método, interface, classe ou campo, podemos:

  1. Informe o compilador sobre avisos e erros

  2. Manipular código fonte no momento da compilação

  3. Modificar ou examinar o comportamento em tempo de execução

3. Java Built-in Annotations

Agora que revisamos o básico, vamos dar uma olhada em algumas anotações que vêm com o Java principal. Primeiro, existem vários que informam a compilação:

  1. @Sobrepor

  2. @Suprimir avisos

  3. @Descontinuada

  4. @SafeVarargs

  5. @FunctionalInterface

Essas anotações geram ou suprimem avisos e erros do compilador. Aplicá-los de forma consistente geralmente é uma boa prática, pois adicioná-los pode impedir futuros erros do programador.

A anotação@Override é usada para indicar que um método sobrescreve ou substitui o comportamento de um método herdado.

@SuppressWarnings indica que queremos ignorar certos avisos de uma parte do código. A anotação@SafeVarargs também atua em um tipo de aviso relacionado ao uso de varargs.

A anotação@Deprecated pode ser usada para marcar uma API como não destinada a ser usada mais.

Por tudo isso, você pode encontrar informações mais detalhadas nos artigos vinculados.

3.1. @FunctionalInterface

O Java 8 nos permite escrever código de uma maneira mais funcional.

Single Abstract Method interfaces são uma grande parte disso. If we intend a SAM interface to be used by lambdas, we can optionally mark it as such with @FunctionalInterface:

@FunctionalInterface
public interface Adder {
    int add(int a, int b);
}

Como@Override com métodos,@FunctionalInterface declara nossas intenções comAdder.

Agora, quer usemos@FunctionalInterface ou não, ainda podemos usarAdder da mesma maneira:

Adder adder = (a,b) -> a + b;
int result = adder.add(4,5);

Mas, se adicionarmos um segundo método aAdder,, o compilador reclamará:

@FunctionalInterface
public interface Adder {
    // compiler complains that the interface is not a SAM

    int add(int a, int b);
    int div(int a, int b);
}

Agora, isso teria sido compilado sem a anotação@FunctionalInterface. Então, o que isso nos dá?

Como@Override, essa anotação nos protege contra erros futuros do programador. Even though it’s legal to have more than one method on an interface, it isn’t when that interface is being used as a lambda target. Sem essa anotação, o compilador iria quebrar nas dezenas de lugares ondeAdder foi usado como lambda. Agora,it just breaks in Adder itself.

4. Meta-anotações

Em seguida, as meta-anotações são anotações que podem ser aplicadas a outras anotações.

Por exemplo, essas meta-anotações são usadas para a configuração da anotação:

  1. @Alvo

  2. @Retenção

  3. @Inherited

  4. @Documented

  5. @Repetivel

4.1. @Target

O escopo das anotações pode variar com base nos requisitos. Enquanto uma anotação é usada apenas com métodos, outra anotação pode ser consumida com declarações de construtor e de campo.

Para determinar os elementos de destino de uma anotação personalizada, precisamos rotulá-la com uma anotação@Target.

@Target pode trabalhar comeight different element types. Se olharmos o código-fonte de @SafeVarargs, então podemos ver que ele deve ser anexado apenas a construtores ou métodos:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}

4.2. @Retention

Algumas anotações devem ser usadas como dicas para o compilador, enquanto outras são usadas em tempo de execução.

We use the @Retention annotation to say where in our program’s lifecycle our annotation applies.

Para fazer isso, precisamos configurar@Retention com uma das três políticas de retenção:

  1. RetentionPolicy.SOURCE – visível nem pelo compilador nem pelo tempo de execução

  2. RetentionPolicy.CLASS - visível pelo compilador

  3. RetentionPolicy.RUNTIME – visível pelo compilador e pelo tempo de execução

@Retention padroniza paraRetentionPolicy.SOURCE.

Se tivermos uma anotação que deve estar acessível em tempo de execução:

@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE)
public @interface RetentionAnnotation {
}

Então, se adicionarmos anotações a uma classe:

@RetentionAnnotation
@Deprecated
public class AnnotatedClass {
}

Agora podemos refletir sobreAnnotatedClass para ver quantas anotações são retidas:

@Test
public void whenAnnotationRetentionPolicyRuntime_shouldAccess() {
    AnnotatedClass anAnnotatedClass = new AnnotatedClass();
    Annotation[] annotations = anAnnotatedClass.getClass().getAnnotations();
    assertThat(annotations.length, is(1));
}

O valor é 1 porque @RetentionAnnotation tem uma política de retenção deRUNTIME, enquanto@Deprecated não.

4.3. @Inherited

Em algumas situações, podemos precisar de uma subclasse para vincular as anotações a uma classe pai.

Podemos usar a anotação@Inherited para fazer nossa anotação se propagar de uma classe anotada para suas subclasses.

Se aplicarmos@Inherited à nossa anotação personalizada e depois aplicá-la aBaseClass:

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {
}

@InheritedAnnotation
public class BaseClass {
}

public class DerivedClass extends BaseClass {
}

Então, depois de estender a BaseClass, devemos ver queDerivedClass parece ter a mesma anotação em tempo de execução:

@Test
public void whenAnnotationInherited_thenShouldExist() {
    DerivedClass derivedClass = new DerivedClass();
    InheritedAnnotation annotation = derivedClass.getClass()
      .getAnnotation(InheritedAnnotation.class);

    assertThat(annotation, instanceOf(InheritedAnnotation.class));
}

Sem a anotação@Inherited, o teste acima falharia.

4.4. @Documented

Por padrão, Java não documenta o uso de uma anotação em Javadocs.

But, we can use the @Documented annotation to change Java’s default behavior.

Se criarmos uma anotação personalizada que usa@Documented:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCell {
    int value();
}

E, aplique-o ao elemento Java apropriado:

public class Employee {
    @ExcelCell(0)
    public String name;
}

Em seguida, o JavadocEmployee revelará o uso da anotação:

image

4.5. @Repeatable

Às vezes, pode ser útil especificar a mesma anotação mais de uma vez em um determinado elemento Java.

Antes do Java 7, tivemos que agrupar anotações em uma única anotação de contêiner:

@Schedules({
    @Schedule(time = "15:05"),
    @Schedule(time = "23:00")
})
void scheduledAlarm() {
}

No entanto, o Java 7 trouxe uma abordagem mais limpa. Withthe @Repeatable annotation,we can make an annotation repeatable:

@Repeatable(Schedules.class)
public @interface Schedule {
    String time() default "09:00";
}

Para usar@Repeatable, também precisamos ter uma anotação de contêiner. Neste caso, reutilizaremos@Schedules:

public @interface Schedules {
    Schedule[] value();
}

Obviamente, isso se parece muito com o que tínhamos antes do Java 7. Mas, o valor agora é que o wrapper@Schedules não é mais especificado quando precisamos repetir@Schedule:

@Schedule
@Schedule(time = "15:05")
@Schedule(time = "23:00")
void scheduledAlarm() {
}

Como o Java requer a anotação do wrapper, foi fácil migrar das listas de anotações anteriores ao Java 7 para anotações repetíveis.

5. Conclusão

Neste artigo, falamos sobre as anotações integradas do Java com as quais todo desenvolvedor Java deve estar familiarizado.

Como sempre, todos os exemplos do artigo podem ser encontradosover on GitHub.