Panoramica delle annotazioni integrate in Java

1. Panoramica

In questo articolo, parleremo di una funzionalità principale del linguaggio Java: le annotazioni predefinite disponibili nel JDK.

2. Che cos'è un'annotazione

In poche parole, le annotazioni sono tipi Java preceduti da un simbolo "@" .

Java ha avuto annotazioni sin dalla versione 1.5. Da allora, hanno plasmato il modo in cui abbiamo progettato le nostre applicazioni.

Spring e Hibernate sono ottimi esempi di framework che fanno molto affidamento sulle annotazioni per abilitare varie tecniche di progettazione.

Fondamentalmente, un'annotazione assegna metadati extra al codice sorgente a cui è associata . Aggiungendo un'annotazione a un metodo, interfaccia, classe o campo, possiamo:

  1. Informa il compilatore di avvisi ed errori
  2. Manipola il codice sorgente al momento della compilazione
  3. Modifica o esamina il comportamento in fase di esecuzione

3. Annotazioni incorporate di Java

Ora che abbiamo esaminato le basi, diamo un'occhiata ad alcune annotazioni fornite con Java core. Innanzitutto, ce ne sono diversi che informano la compilazione:

  1. @Oltrepassare
  2. @SuppressWarnings
  3. @ Deprecato
  4. @SafeVarargs
  5. @FunctionalInterface
  6. @Native

Queste annotazioni generano o sopprimono gli avvisi e gli errori del compilatore. Applicarli in modo coerente è spesso una buona pratica poiché aggiungerli può prevenire futuri errori del programmatore.

L' annotazione @Override viene utilizzata per indicare che un metodo sovrascrive o sostituisce il comportamento di un metodo ereditato.

@SuppressWarnings indica che vogliamo ignorare determinati avvisi da una parte del codice. L'annotazione @SafeVarargs agisce anche su un tipo di avviso relativo all'uso di varargs.

L' annotazione @Deprecated può essere utilizzata per contrassegnare un'API come non più destinata all'uso. Inoltre, questa annotazione è stata adattata in Java 9 per rappresentare ulteriori informazioni sulla deprecazione.

Per tutti questi, puoi trovare informazioni più dettagliate negli articoli collegati.

3.1. @FunctionalInterface

Java 8 ci consente di scrivere codice in modo più funzionale.

Le interfacce Single Abstract Method sono una parte importante di questo. Se intendiamo che un'interfaccia SAM venga utilizzata da lambda, possiamo opzionalmente contrassegnarla come tale con @FunctionalInterface :

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

Come @Override con i metodi, @FunctionalInterface dichiara le nostre intenzioni con Adder .

Ora, sia che usiamo @FunctionalInterface o meno, possiamo ancora usare Adder allo stesso modo:

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

Ma, se aggiungiamo un secondo metodo a Adder, il compilatore si lamenterà:

@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); }

Ora, questo sarebbe stato compilato senza l' annotazione @FunctionalInterface . Allora, cosa ci dà?

Come @Override , questa annotazione ci protegge da futuri errori del programmatore. Anche se è legale avere più di un metodo su un'interfaccia, non è quando quell'interfaccia viene utilizzata come target lambda. Senza questa annotazione, il compilatore si interromperà nelle dozzine di punti in cui Adder è stato utilizzato come lambda. Ora, si rompe solo in Adder stesso.

3.2. @Native

As of Java 8, there is a new annotation in the java.lang.annotation package called Native. The @Native annotation is only applicable to fields. It indicates the annotated field is a constant that may be referenced from the native code. For instance, here's how it's used in the Integer class:

public final class Integer { @Native public static final int MIN_VALUE = 0x80000000; // omitted }

This annotation can also serve as a hint for the tools to generate some auxiliary header files.

4. Meta-Annotations

Next, meta-annotations are annotations that can be applied to other annotations.

For example, these meta-annotations are used for annotation configuration:

  1. @Target
  2. @Retention
  3. @Inherited
  4. @Documented
  5. @Repeatable

4.1. @Target

The scope of annotations can vary based on the requirements. While one annotation is only used with methods, another annotation can be consumed with constructor and field declarations.

To determine the target elements of a custom annotation, we need to label it with a @Target annotation.

@Target can work with eight different element types. If we look at the source code of @SafeVarargs, then we can see that it must be only attached to constructors or methods:

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

4.2. @Retention

Some annotations are meant to be used hints for the compiler, while others are used at runtime.

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

To do this, we need to configure @Retention with one of three retention policies:

  1. RetentionPolicy.SOURCE – visible by neither the compiler nor the runtime
  2. RetentionPolicy.CLASS – visible by the compiler
  3. RetentionPolicy.RUNTIME – visible by the compiler and the runtime

@Retention defaults to RetentionPolicy.SOURCE.

If we have an annotation that should be accessible at runtime:

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

Then, if we add some annotations to a class:

@RetentionAnnotation @Deprecated public class AnnotatedClass { }

Now we can reflect on AnnotatedClass to see how many annotations are retained:

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

The value is 1 because @RetentionAnnotation has a retention policy of RUNTIME while @Deprecated doesn't.

4.3. @Inherited

In some situations, we may need a subclass to have the annotations bound to a parent class.

We can use the @Inherited annotation to make our annotation propagate from an annotated class to its subclasses.

If we apply @Inherited to our custom annotation and then apply it to BaseClass:

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

Then, after extending the BaseClass, we should see that DerivedClass appears to have the same annotation at runtime:

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

Without the @Inherited annotation, the above test would fail.

4.4. @Documented

By default, Java doesn't document the usage of an annotation in Javadocs.

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

If we create a custom annotation that uses @Documented:

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

And, apply it to the appropriate Java element:

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

Then, the Employee Javadoc will reveal the annotation usage:

4.5. @Repeatable

Sometimes it can be useful to specify the same annotation more than once on a given Java element.

Before Java 7, we had to group annotations together into a single container annotation:

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

However, Java 7 brought a cleaner approach. With the @Repeatable annotation, we can make an annotation repeatable:

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

To use @Repeatable, we need to have a container annotation, too. In this case, we'll reuse @Schedules:

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

Of course, this looks a lot like what we had before Java 7. But, the value now is that the wrapper @Schedules isn't specified anymore when we need to repeat @Schedule:

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

Because Java requires the wrapper annotation, it was easy for us to migrate from pre-Java 7 annotation lists to repeatable annotations.

5. Conclusion

In questo articolo, abbiamo parlato delle annotazioni integrate di Java con cui ogni sviluppatore Java dovrebbe avere familiarità.

Come sempre, tutti gli esempi dell'articolo sono disponibili su GitHub.