Java Type Annotations

Beginning with JDK 8, the places in which the annotations can be used has been expanded. As mentioned earlier, annotations were originally allowed only on declarations. However, with the advent of JDK 8, annotations can also be specified in most of the cases in which a type is used. This expanded aspect of annotations is called as type annotation. For example, you can annotate the return type of a method, the type of this within a method, a cast, array levels, an inherited class, and a throws clause. You can also annotate the generic types, including generic type parameter bounds and generic type arguments.

Type annotations are important because they enable tools to perform additional checks on code to help prevent the errors. Understand that, as a general rule, javac will not perform these checks itself. A separate tool is used for this purpose, although such a tool might operate as a compiler plug-in.

A type annotation must include the ElementType.TYPE_USE as a target. From earlier discussions, valid annotation targets are specified using the @Target annotation. A type annotation applies to the type that the annotation precedes. For example, assuming some type annotation called @TypeAnnotat, the following is legal:

void myMethod() throws @TypeAnnotat NullPointerException { // ...

Here, the @TypeAnnotat annotates the NullPointerException in the throws clause.

You can also annotate the type of this (called the receiver). As you know, this is an implicit argument to all the instance methods and it refers to the invoking object. To annotate its type requires the use of another new JDK 8 feature. Beginning with JDK 8, you can explicitly declare this as the first parameter to a method. In the following declaration, the type of this must be the type of its class, for example:

class MyClass {
   int myMethod(MyClass this, int i, int j) { // ...

Here, because myMethod() is a method defined by MyClass, the type of this is MyClass. Using this declaration, you can now annotate the type of this. For example, again assuming that the @TypeAnnotat is a type annotation, the following is legal:

int myMethod(@TypeAnnotat MyClass this, int i, int j) { // ...

It is important to understand that it is not necessary to declare this unless you are annotating it. If this is not declared, it is still implicitly passed. JDK 8 doesn't change this fact. Also, explicitly declaring this doesn't change any aspect of the method's signature because this is implicitly declared, by default. Again, you will declare this only if you want to apply a type annotation to it. If you do declare this, it must be the first parameter.

The upcoming program shows a number of the places that a type annotation can be used. It defines the several annotations, of which several are for type annotation. The names and targets of the annotations are shown in the following table:

Annotation Target
@TypeAnnotat ElementType.TYPE_USE
@MaxLen ElementType.TYPE_USE
@NotZeroLen ElementType.TYPE_USE
@Unique ElementType.TYPE_USE
@What ElementType.TYPE_PARAMETER
@EmptyOK ElementType.FIELD
@Recommended ElementType.METHOD

Notice that @EmptyOK, @Recommended, and @What are not type annotations. They are included for the comparison purposes. Of special interest is @What, which is used to annotate a generic type parameter declaration and is another new annotation feature added by JDK 8.

Java Type Annotations Example

Here is an example program, demonstrates the type annotations in Java. The comments in the program described each use:

/* Java Program Example - Java Type Annotations 
*  This program demonstrate several type annotations */

import java.lang.annotation.*;
import java.lang.reflect.*;

/* a marker annotation that can be applied to a type */
@Target(ElementType.TYPE_USE)
@interface TypeAnnotat { }

/* another marker annotation that can be applied to a type */
@Target(ElementType.TYPE_USE)
@interface NotZeroLen { }

/* still another marker annotation that can be applied to a type */
@Target(ElementType.TYPE_USE)
@interface Unique { }

/* a parameterized annotation that can be applied to a type */
@Target(ElementType.TYPE_USE)
@interface MaxLen {
   int value();
}

/* an annotation that can be applied to a type parameter */
@Target(ElementType.TYPE_PARAMETER)
@interface What {
   String description();
}

/* an annotation that can be applied to a field declaration */
@Target(ElementType.FIELD)
@interface EmptyOK { }

/* an annotation that can be applied to a method declaration */
@Target(ElementType.METHOD)
@interface Recommended { }

/* use an annotation on a type parameter */
class TypeAnnotatDemo<@What(description = "Generic data type") T> {

   /* use a type annotation on a constructor */
   public @Unique TypeAnnotatDemo() { }
   
   /* annotate the type (in this case String), not the field */
   @TypeAnnotat String str;
   
   /* this annotates the field test */
   @EmptyOK String test;
   
   /* use a type annotation to annotate this (the receiver) */
   public int fun(@TypeAnnotat TypeAnnotatDemo<T> this, int x) {
      return 10;
   }
   
   /* annotate the return type */
   public @TypeAnnotat Integer fun2(int j, int k) {
      return j+k;
   }
   
   /* annotate the method declaration */
   public @Recommended Integer fun3(String str) {
      return str.length() / 2;
   }
   
   /* use a type annotation with a throws clause */
   public void fun4() throws @TypeAnnotat NullPointerException {
      // ...
   }
   
   /* annotate array levels */
   String @MaxLen(10) [] @NotZeroLen [] w;
   
   /* annotate the array element type */
   @TypeAnnotat Integer[] vec;
   
   public static void myMethod(int i) {
      
      /* use a type annotation on a type argument */
      TypeAnnotatDemo<@TypeAnnotat Integer> obj = new TypeAnnotatDemo<@TypeAnnotat Integer>();
      
      /* use a type annotation with new */
      @Unique TypeAnnotatDemo<Integer> obj2 = new @Unique TypeAnnotatDemo<Integer>();
      
      Object x = new Integer(10);
      Integer y;
      
      /* use a type annotation on a cast */
      y = (@TypeAnnotat Integer) x;
   }
   
   public static void main(String args[])
   {
      myMethod(10);
   }
   
   /* use type annotation with inheritance clause */
   class MyClass extends @TypeAnnotat TypeAnnotatDemo<Boolean> { }
}

Although what most of the annotations in the above program refer to is clear, four uses require a bit of discussion. The first is the annotation of a method return type versus the annotation of a method declaration. In the program, pay special attention to these two method declarations :

/* annotate the return type */
public @TypeAnnotat Integer fun2(int j, int k) {
   return j+k;
}

/* annotate the method declaration */
public @Recommended Integer fun3(String str) {
   return str.length() / 2;
}

Notice that in both the cases, an annotation precedes the method's return type which is Integer. However, the two annotations annotate the two different things. In first case, the @TypeAnnotat annotation annotates the fun2()'s return type. This is because the @TypeAnnotat has its target specified as ElementType.TYPE_USE, which means that it can be used to annotate type uses. In second case, the @Recommended annotates the method declaration itself. This is because the @Recommended has its target specified as ElementType.METHOD. As a result, @Recommended applies to the declaration, not the return type. Therefore, the target specification is used to eliminate what, at first glance, appears to be ambiguity between the annotation of a method declaration and the annotation of the method's return type.

One other things about annotating a method return type, you can not annotate a return type of void.

The second point of interest are the field annotations, shown here:

/* annotate the type (in this case String), not the field
@TypeAnnotat String str;

/* this annotates the field test */
@EmptyOK String test;

Here, @TypeAnnotat annotates the type String, but @EmptyOK annotates the field test. Even though both the annotations precede the entire declaration, their targets are different, based on the target element type. If the annotation has the ElementType.TYPE_USE target, then the type is annotated. If it has ElementType.FIELD as a target, then the field is annotated. Thus, the situation is similar to that just described for the methods, and no ambiguity exists. The same mechanism also disambiguates annotations on the local variables.

Next, notice how this (the receiver) is annotated here:

public int fun(@TypeAnnotat TypeAnnotatDemo<T> this, int x) {

Here, this is specified as the first parameter and is of the type TypeAnnotatDemo (which is the class of which the fun() method is a member). As explained, beginning with JDK 8, an instance method declaration can explicitly specify the this parameter for the sake of applying a type annotation.

Finally, look at how array levels are annotated by the following statement :

String @MaxLen(10) [] @NotZeroLen [] w;

In this declaration, @MaxLen annotates the type of the first level and @NotZeroLen annotates the type of the second level. In this declaration

@TypeAnnotat Integer[] vec;

the element type Integer is annotated.

Java Online Test


« Previous Tutorial Next Tutorial »