1. Resumen
En este artículo, hablaremos sobre una característica principal del lenguaje Java: las anotaciones predeterminadas disponibles en el JDK.
2. Qué es una anotación
En pocas palabras, las anotaciones son tipos de Java que están precedidos por un símbolo "@" .
Java ha tenido anotaciones desde la versión 1.5. Desde entonces, han dado forma a la forma en que diseñamos nuestras aplicaciones.
Spring e Hibernate son excelentes ejemplos de marcos que dependen en gran medida de las anotaciones para habilitar varias técnicas de diseño.
Básicamente, una anotación asigna metadatos adicionales al código fuente al que está vinculado . Al agregar una anotación a un método, interfaz, clase o campo, podemos:
- Informar al compilador sobre advertencias y errores
- Manipular el código fuente en el momento de la compilación
- Modificar o examinar el comportamiento en tiempo de ejecución
3. Anotaciones integradas de Java
Ahora que hemos revisado los conceptos básicos, echemos un vistazo a algunas anotaciones que se incluyen con el núcleo de Java. Primero, hay varios que informan la compilación:
- @Anular
- @SuppressWarnings
- @Obsoleto
- @SafeVarargs
- @FunctionalInterface
- @Nativo
Estas anotaciones generan o suprimen advertencias y errores del compilador. Aplicarlos consistentemente es a menudo una buena práctica, ya que agregarlos puede evitar futuros errores del programador.
La anotación @Override se utiliza para indicar que un método anula o reemplaza el comportamiento de un método heredado.
@SuppressWarnings indica que queremos ignorar ciertas advertencias de una parte del código. Laanotación @SafeVarargs también actúa sobre un tipo de advertencia relacionada con el uso de varargs.
La anotación @Deprecated se puede usar para marcar una API como ya no está diseñada para su uso. Además, esta anotación se ha actualizado en Java 9 para representar más información sobre la desaprobación.
Para todos estos, puede encontrar información más detallada en los artículos vinculados.
3.1. @FunctionalInterface
Java 8 nos permite escribir código de una forma más funcional.
Las interfaces del método abstracto único son una gran parte de esto. Si pretendemos que una interfaz SAM sea utilizada por lambdas, opcionalmente podemos marcarla como tal con @FunctionalInterface :
@FunctionalInterface public interface Adder { int add(int a, int b); }
Al igual que @Override con métodos, @FunctionalInterface declara nuestras intenciones con Adder .
Ahora, ya sea que usemos @FunctionalInterface o no, aún podemos usar Adder de la misma manera:
Adder adder = (a,b) -> a + b; int result = adder.add(4,5);
Pero, si agregamos un segundo método a Adder, el compilador se quejará:
@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); }
Ahora, esto se habría compilado sin la anotación @FunctionalInterface . Entonces, ¿qué nos da?
Como @Override , esta anotación nos protege contra futuros errores del programador. Aunque es legal tener más de un método en una interfaz, no lo es cuando esa interfaz se usa como un objetivo lambda. Sin esta anotación, el compilador se rompería en las docenas de lugares donde se usó Adder como lambda. Ahora, simplemente se rompe en el propio Adder .
3.2. @Nativo
A partir de Java 8, hay una nueva anotación en el paquete java.lang.annotation llamada Native. La anotación @Native solo se aplica a los campos. Indica que el campo anotado es una constante a la que se puede hacer referencia desde el código nativo . Por ejemplo, así es como se usa en la clase Integer :
public final class Integer { @Native public static final int MIN_VALUE = 0x80000000; // omitted }
Esta anotación también puede servir como una pista para que las herramientas generen algunos archivos de encabezado auxiliares.
4. Meta-anotaciones
A continuación, las meta-anotaciones son anotaciones que se pueden aplicar a otras anotaciones.
Por ejemplo, estas meta-anotaciones se utilizan para la configuración de anotaciones:
- @Objetivo
- @Retencion
- @Heredado
- @Documentado
- @Repetible
4.1. @Objetivo
El alcance de las anotaciones puede variar según los requisitos. Mientras que una anotación solo se usa con métodos, otra anotación se puede consumir con declaraciones de constructor y campo.
Para determinar los elementos de destino de una anotación personalizada, debemos etiquetarla con una anotación @Target .
@Target puede trabajar con ocho tipos de elementos diferentes. Si miramos el código fuente de @ SafeVarargs , entonces podemos ver que solo debe adjuntarse a constructores o métodos:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface SafeVarargs { }
4.2. @Retencion
Algunas anotaciones están destinadas a ser sugerencias para el compilador, mientras que otras se usan en tiempo de ejecución.
Usamos la anotación @Retention para indicar en qué parte del ciclo de vida de nuestro programa se aplica nuestra anotación .
To do this, we need to configure @Retention with one of three retention policies:
- RetentionPolicy.SOURCE – visible by neither the compiler nor the runtime
- RetentionPolicy.CLASS – visible by the compiler
- 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
En este artículo, hemos hablado de las anotaciones integradas de Java con las que todo desarrollador de Java debería estar familiarizado.
Como siempre, todos los ejemplos del artículo se pueden encontrar en GitHub.