Skip to content

Latest commit

 

History

History
412 lines (317 loc) · 11.7 KB

java-annotation-training.adoc

File metadata and controls

412 lines (317 loc) · 11.7 KB

Java annotation training

Overview

Java annotations were introduced in Java 5.

They are used to provide metadata on your Java code.
As a consequence, they have no direct effect on the operation of the code they annotate.

They are generally used for the following purposes:

  • Information for the compiler: Annotations can be used by the compiler to detect errors or suppress warnings.

  • Compile-time and deployment-time (meaning build-time) processing: Software tools (like Maven) can process annotation information to generate code, XML files, and so forth.

  • Runtime processing: Some annotations are available to be examined at runtime, usually through Java Reflection.

Annotation basics

  • Shortest form, with no element:

    @Entity

    The @ character indicates to the compiler that it is an annotation, after that comes the annotation name.

  • With elements:

    @Entity(tableName = "vehicles", primaryKey = "id")

    tableName and primaryKey are the names of the 2 elements, whose values are respectively set to vehicules and id.
    If there is just one element named value (the common convention in that case), then the name can be omitted:

    @SuppressWarnings("unchecked")

Where annotations can be used

Annotations can be placed above declarations:

  • declarations of classes,

  • methods,

  • methods parameters,

  • fields,

  • and local variables

Here is an example (with non really existing annotations):

@Entity
public class Vehicle {

    @Persistent
    protected String vehicleName = null;


    @Getter
    public String getVehicleName() {
        return this.vehicleName;
    }

    public void setVehicleName(@Optional vehicleName) {
        this.vehicleName = vehicleName;
    }

    public List addVehicleNameToList(List names) {

        @Optional
        List localNames = names;

        if(localNames == null) {
            localNames = new ArrayList();
        }
        localNames.add(getVehicleName());

        return localNames;
    }

}

Type annotations

Note

Java 8 introduced the possibility to apply annotations to the use of types, which is called type annotation.

Here are some examples:

  • Class instance creation expression:

    new @Interned MyObject();
  • Type cast:

    myString = (@NonNull String) str;
  • implements clause:

    class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }
  • Thrown exception declaration:

    void monitorTemperature() throws @Critical TemperatureException { ... }

Type annotations were created to support improved analysis of Java programs, so as to ensure stronger type checking.
For example, you want to ensure that a particular variable in your program is never assigned to null, and you write a custom plugin handling your custom @NonNull annotation.

Several third parties already wrote this kind of type checking modules, like the Checker Framework.

Built-in Java annotations

Several built-in annotations exist:

  • @Deprecated: indicates that the marked element is deprecated and should no longer be used.
    Do not mistake it with the @deprecated Javadoc symbol.

    @Deprecated
    /**
      @deprecated Use MyNewComponent instead.
    */
    public class MyComponent {
    
    }
  • @Override: informs the compiler that the element is meant to override an element declared in a superclass

  • @SuppressWarnings: tells the compiler to suppress specific warnings that it would otherwise generate

  • @SafeVarargs: when applied to a method or constructor, asserts that the code does not perform potentially unsafe operations on its varargs parameter.
    When this annotation type is used, unchecked warnings relating to varargs usage are suppressed.

Besides those last "classic" built-in annotations, also exist meta-annotations, which apply to other annotations:

  • @Retention

  • @Documented

  • @Target

  • @Inherited

  • @Repeatable

Those annotations are explained below.

Creating your own annotations

Creating a custom annotation means defining an annotation type.
Here is the syntax to do so:

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

An annotation type is defined in its own file, just like a Java class or interface.
You define it using the @interface keyword.

The data types of its elements can be:

  • primitives,

  • arrays,

  • but NOT complex objects

Once defined, you can use annotations of this type the following way:

@ClassPreamble (
   author = "John Doe",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "Jane Doe",
   // Note array notation
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {

// class code goes here

}

Element default values

Element default values can be specified using the keyword default, which makes the element become optional.

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   ...

Without a default value, you must explicitly specify a value for all the elements of the annotation.

@Retention

@Retention annotation specifies how the marked annotation is stored:

  • RetentionPolicy.SOURCE: The marked annotation is retained only in the source code, and is ignored by the compiler (meaning not stored in .class file, and not available at runtime).
    An example of use is build tools that scan the code, that way the .class files are not polluted unnecessarily.

  • RetentionPolicy.CLASS: The marked annotation is retained by the compiler at compile time (and so is stored in the .class file), but is ignored by the JVM.
    This is the default retention policy, if none is specified.

  • RetentionPolicy.RUNTIME: The marked annotation is retained by the JVM so it can be used at runtime, by reflection.
    Example:

    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    @interface SampleAnnotation {
        String value() default "";
    }

@Target

@Target annotation marks another annotation to restrict what kind of Java elements the annotation can be applied to.

Example:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface SampleAnnotation {
    String value() default "";
}

The possible targets are the following ones:

  • ElementType.ANNOTATION_TYPE: can be applied to an annotation type.

  • ElementType.CONSTRUCTOR: can be applied to a constructor.

  • ElementType.FIELD: can be applied to a field or property.

  • ElementType.LOCAL_VARIABLE: can be applied to a local variable.

  • ElementType.METHOD: can be applied to a method-level annotation.

  • ElementType.PACKAGE: can be applied to a package declaration.

  • ElementType.PARAMETER: can be applied to the parameters of a method.

  • ElementType.TYPE: can be applied to any element of a class.

@Inherited

@Inherited annotation indicates that the annotation used in a class should be inherited by subclasses inheriting from that class.

Example:

java.lang.annotation.Inherited

@Inherited
public @interface SampleAnnotation {

}
@SampleAnnotation
public class SampleSuperClass { ... }
public class SampleSubClass extends SampleSuperClass { ... }
Caution

@Inherited has no effect if the annotated type is used to annotate anything other than a class!

@Documented

@Documented annotation indicates that whenever the specified annotation is used those elements should be documented using the Javadoc tool. (By default, annotations are not included in Javadoc.)

Example:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface SampleAnnotation {
    String value() default "";
}

@Repeatable

Introduced in Java 8, repeating annotations enable you to apply several times the same annotation to a declaration, or type use.

Example:

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }

Repeating annotations are stored in a container annotation that is automatically generated by the Java compiler.
As a consequence, besides the declaration of the annotation type using @Repeatable, you also have to declare a containing annotation type.

The "classic" annotation type declaration:

import java.lang.annotation.Repeatable;

@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}

Schedules.class is the type of the container annotation that the Java compiler generates to store repeating annotations.

And so, here is the declaration of the containing annotation type:

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

The containing annotation type must have a value element with an array type.
The component type of the array type must be the repeatable annotation type.

Annotations tricks and tips

Inheritance confusion

There is NO inheritance for annotations, except for classes, using the @Inherited meta-annotation.

Differences between getAnnotations and getDeclaredAnnotations for Class, Method and Field

The "hack" is that there is NO difference for those methods for Method and Field.

This can be found by looking at the source code, for those 2 classes, getAnnotations is inherited from java.lang.reflect.AccessibleObject, with the following code:

java.lang.reflect.AccessibleObject
/**
 * @since 1.5
 */
public Annotation[] getAnnotations() {
    return getDeclaredAnnotations();
}

But, for class, getAnnotations is overriden. In this case, as explained in the Javadoc, you have:

  • getAnnotations: Returns annotations that are present on this element. If there are no annotations present on this element, the return value is an array of length 0.

  • getDeclaredAnnotations: Returns this element’s annotation for the specified type if such an annotation is directly present, else null. This method ignores inherited annotations. (Returns null if no annotations are directly present on this element.)

To illustrate those differences, have a look at the AnnotationInheritance in my GitHub repo.

Retrieving the name of a parameter through an annotation

In fact, it is not (easily) possible to get the name of a parameter through an annotation.

The only way to do so is to told the compiler to include them, by compiling with debug information (the -g option of javac).