Présentation des annotations intégrées Java

1. Aperçu

Dans cet article, nous parlerons d'une fonctionnalité principale du langage Java - les annotations par défaut disponibles dans le JDK.

2. Qu'est-ce qu'une annotation

En termes simples, les annotations sont des types Java qui sont précédés d'un symbole «@» .

Java a eu des annotations depuis la version 1.5. Depuis, ils ont façonné la façon dont nous concevons nos applications.

Spring et Hibernate sont d'excellents exemples de frameworks qui reposent fortement sur les annotations pour permettre diverses techniques de conception.

Fondamentalement, une annotation attribue des métadonnées supplémentaires au code source auquel elle est liée . En ajoutant une annotation à une méthode, une interface, une classe ou un champ, nous pouvons:

  1. Informer le compilateur des avertissements et des erreurs
  2. Manipuler le code source au moment de la compilation
  3. Modifier ou examiner le comportement lors de l'exécution

3. Annotations intégrées Java

Maintenant que nous avons passé en revue les bases, jetons un coup d'œil à quelques annotations fournies avec le noyau Java. Premièrement, il y en a plusieurs qui informent la compilation:

  1. @Passer outre
  2. @Supprimer les avertissements
  3. @Deprecated
  4. @SafeVarargs
  5. @Interface fonctionnelle
  6. @Originaire de

Ces annotations génèrent ou suppriment les avertissements et les erreurs du compilateur. Les appliquer de manière cohérente est souvent une bonne pratique, car leur ajout peut éviter une erreur future du programmeur.

L' annotation @Override est utilisée pour indiquer qu'une méthode remplace ou remplace le comportement d'une méthode héritée.

@SuppressWarnings indique que nous voulons ignorer certains avertissements d'une partie du code. L'annotation @SafeVarargs agit également sur un type d'avertissement lié à l'utilisation de varargs.

L' annotation @Deprecated peut être utilisée pour marquer une API comme n'étant plus destinée à être utilisée. De plus, cette annotation a été mise à niveau dans Java 9 pour représenter plus d'informations sur la dépréciation.

Pour tout cela, vous pouvez trouver des informations plus détaillées dans les articles liés.

3.1. @Interface fonctionnelle

Java 8 nous permet d'écrire du code de manière plus fonctionnelle.

Les interfaces de méthode abstraite unique en sont une grande partie. Si nous prévoyons qu'une interface SAM soit utilisée par les lambdas, nous pouvons éventuellement la marquer comme telle avec @FunctionalInterface :

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

Comme @Override avec des méthodes, @FunctionalInterface déclare nos intentions avec Adder .

Maintenant, que nous utilisions @FunctionalInterface ou non, nous pouvons toujours utiliser Adder de la même manière:

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

Mais, si nous ajoutons une deuxième méthode à Adder, le compilateur se plaindra:

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

Maintenant, cela aurait été compilé sans l' annotation @FunctionalInterface . Alors, qu'est-ce que ça nous donne?

Comme @Override , cette annotation nous protège contre les futures erreurs du programmeur. Même s'il est légal d'avoir plus d'une méthode sur une interface, ce n'est pas le cas lorsque cette interface est utilisée comme cible lambda. Sans cette annotation, le compilateur se briserait dans les dizaines d'endroits où Adder était utilisé comme lambda. Maintenant, il se brise dans Adder lui-même.

3.2. @Originaire de

À partir de Java 8, il existe une nouvelle annotation dans le package java.lang.annotation appelée Native. L' annotation @Native ne s'applique qu'aux champs. Il indique que le champ annoté est une constante qui peut être référencée à partir du code natif . Par exemple, voici comment il est utilisé dans la classe Integer :

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

Cette annotation peut également servir d'indication aux outils pour générer des fichiers d'en-tête auxiliaires.

4. Méta-annotations

Ensuite, les méta-annotations sont des annotations qui peuvent être appliquées à d'autres annotations.

Par exemple, ces méta-annotations sont utilisées pour la configuration des annotations:

  1. @Cible
  2. @Rétention
  3. @Hérité
  4. @Documenté
  5. @Répétable

4.1. @Cible

La portée des annotations peut varier en fonction des exigences. Alors qu'une annotation n'est utilisée qu'avec des méthodes, une autre annotation peut être utilisée avec des déclarations de constructeur et de champ.

Pour déterminer les éléments cibles d'une annotation personnalisée, nous devons l'étiqueter avec une annotation @Target .

@Target peut fonctionner avec huit types d'éléments différents. Si nous regardons le code source de @ SafeVarargs , nous pouvons voir qu'il ne doit être attaché qu'à des constructeurs ou des méthodes:

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

4.2. @Rétention

Certaines annotations sont destinées à être utilisées pour le compilateur, tandis que d'autres sont utilisées à l'exécution.

Nous utilisons l' annotation @Retention pour indiquer où dans le cycle de vie de notre programme notre annotation s'applique .

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

Dans cet article, nous avons parlé des annotations intégrées Java que tout développeur Java devrait connaître.

Comme toujours, tous les exemples de l'article se trouvent sur GitHub.