Java 101: The Next Generation

Java 101: The essential Java language features tour, Part 2

Programming with typesafe enums and annotations in Java 5

Java 101: The Next Generation

Show More
1 2 Page 2
Page 2 of 2

Listing 6 assigns a metadata item to each element; for example, 1000 is assigned to id. Unlike coder, the id and finishDate elements must be specified; otherwise, the compiler will report an error. When coder isn't assigned a value, it assumes its default "n/a" value.

Java provides a special String value() element that can be used to return a comma-separated list of metadata items. Listing 7 demonstrates this element in a refactored version of ToDo.

Listing 7. ToDo.java (version 2)


public @interface ToDo
{
   String value();
}

When value() is an annotation type's only element, you don't have to specify value and the = assignment operator when assigning a string to this element. Listing 8 demonstrates both approaches.

Listing 8. AnnDemo.java (version 3)


public class AnnDemo
{
   public static void main(String[] args)
   {
      String[] cities = { "New York", "Melbourne", "Beijing", "Moscow",
                          "Paris", "London" };
      sort(cities);
   }
   @ToDo(value="1000,10/10/2013,John Doe")
   static void sort(Object[] objects)
   {
   }
   @ToDo("1000,10/10/2013,John Doe")
   static boolean search(Object[] objects, Object key)
   {
      return false;
   }
}

Using meta-annotation types -- the problem of flexibility

You can annotate types (e.g., classes), methods, local variables, and more. However, this flexibility can be problematic. For example, you might want to restrict ToDo to methods only, but nothing prevents it from being used to annotate other application elements, as demonstrated in Listing 9.

Listing 9. AnnDemo.java (version 4)


@ToDo("1000,10/10/2013,John Doe")
public class AnnDemo
{
   public static void main(String[] args)
   {
      @ToDo(value="1000,10/10/2013,John Doe")
      String[] cities = { "New York", "Melbourne", "Beijing", "Moscow",
                          "Paris", "London" };
      sort(cities);
   }
   @ToDo(value="1000,10/10/2013,John Doe")
   static void sort(Object[] objects)
   {
   }
   @ToDo("1000,10/10/2013,John Doe")
   static boolean search(Object[] objects, Object key)
   {
      return false;
   }
}

In Listing 9, ToDo is also used to annotate the AnnDemo class and the cities local variable. The presence of these erroneous annotations might confuse someone reviewing your code, or even your own annotation-processing tools. For the times when you need to narrow an annotation type's flexibility, Java offers the Target annotation type in its java.lang.annotation package.

Target is a meta-annotation type that identifies the kinds of program elements to which an annotation type is applicable. These elements are identified by Target's ElementValue[] value() element.

java.lang.annotation.ElementType is an enum whose constants describe program elements. For example, CONSTRUCTOR applies to constructors and PARAMETER applies to parameters. Listing 10 refactors Listing 7's ToDo annotation type to restrict it to methods only.

Listing 10. ToDo.java (version 3)


import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
public @interface ToDo
{
   String value();
}

Given the refactored ToDo annotation type, an attempt to compile Listing 9 would now result in the following error message:


AnnDemo.java:1: error: annotation type not applicable to this kind of
declaration
@ToDo("1000,10/10/2013,John Doe")
^
AnnDemo.java:6: error: annotation type not applicable to this kind of
declaration
      @ToDo(value="1000,10/10/2013,John Doe")
      ^
2 errors

Additional meta-annotation types

Java 5 supports three additional meta-annotation types, which are found in the java.lang.annotation package:

  • Retention indicates how long annotations with the annotated type are to be retained. This type's associated java.lang.annotation.RetentionPolicy enum declares constants CLASS (compiler records annotations in classfile; virtual machine doesn't retain them to save memory -- default policy), RUNTIME (compiler records annotations in classfile; virtual machine retains them), and SOURCE (compiler discards annotations).
  • Documented indicates that instances of Documented-annotated annotations are to be documented by javadoc and similar tools.
  • Inherited indicates that an annotation type is automatically inherited.

Processing annotations

Annotations are meant to be processed; otherwise, there's no point in having them. Java 5 extended the Reflection API to help you create your own annotation-processing tools. For example, Class declares an Annotation[] getAnnotations() method that returns an array of java.lang.Annotation instances describing annotations present on the element described by the Class object.

Listing 11 presents a simple application that loads a classfile, interrogates its methods for ToDo annotations, and outputs the components of each found annotation.

Listing 11. AnnProcDemo.java


import java.lang.reflect.Method;
public class AnnProcDemo
{
   public static void main(String[] args) throws Exception
   {
      if (args.length != 1)
      {
         System.err.println("usage: java AnnProcDemo classfile");
         return;
      }
      Method[] methods = Class.forName(args[0]).getMethods();
      for (int i = 0; i < methods.length; i++)
      {
         if (methods[i].isAnnotationPresent(ToDo.class))
         {
            ToDo todo = methods[i].getAnnotation(ToDo.class);
            String[] components = todo.value().split(",");
            System.out.printf("ID = %s%n", components[0]);
            System.out.printf("Finish date = %s%n", components[1]);
            System.out.printf("Coder = %s%n%n", components[2]);
         }
      }
   }
}

After verifying that exactly one command-line argument (identifying a classfile) has been specified, main() loads the classfile via Class.forName(), invokes getMethods() to return an array of java.lang.reflect.Method objects identifying all public methods in the classfile, and processes these methods.

Method processing begins by invoking Method's boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) method to determine if the annotation described by ToDo.class is present on the method. If so, Method's <T extends Annotation> T getAnnotation(Class<T> annotationClass) method is called to obtain the annotation.

The ToDo annotations that are processed are those whose types declare a single String value() element (see Listing 7). Because this element's string-based metadata is comma-separated, it needs to be split into an array of component values. Each of the three component values is then accessed and output.

Compile this source code (javac AnnProcDemo.java). Before you can run the application, you'll need a suitable classfile with @ToDo annotations on its public methods. For example, you could modify Listing 8's AnnDemo source code to include public in its sort() and search() method headers. You'll also need Listing 12's ToDo annotation type, which requires the RUNTIME retention policy.

Listing 12. ToDo.java (version 4)


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ToDo
{
   String value();
}

Compile the modified AnnDemo.java and Listing 12, and execute the following command to process AnnDemo's ToDo annotations:


java AnnProcDemo AnnDemo

If all goes well, you should observe the following output:


ID = 1000
Finish date = 10/10/2013
Coder = John Doe
ID = 1000
Finish date = 10/10/2013
Coder = John Doe

Standard annotation types

Along with Target, Retention, Documented, and Inherited, Java 5 introduced java.lang.Deprecated, java.lang.Override, and java.lang.SuppressWarnings. These three annotation types are designed to be used in a compiler context only, which is why their retention policies are set to SOURCE.

Deprecated annotates program elements that are to be deprecated (phased out). Such elements should no longer be used. The compiler will warn you when a deprecated element is being accessed.

The following java.util.Date constructor demonstrates Deprecated:


@Deprecated
public Date(int year, int month, int date, int hrs, int min)
{
   // ... body of this constructor
}

Override annotates subclass methods that override their superclass counterparts. The compiler reports an error when the subclass method doesn't override the superclass method.

The following example demonstrates Override where the java.lang.Runnable interface's public void run() method is overridden by an anonymous class:


Runnable r = new Runnable()
             {
                @Override
                public void run()
                {
                   // ... body of this method
                }
             };

SuppressWarnings annotates program elements (and all elements contained in these program elements) where any of the named compiler warnings (e.g., deprecation and unchecked) should be suppressed.

The following example uses SuppressWarnings to suppress an unchecked warning in the context of the (E[]) cast -- the constructor is annotated instead of the expression containing the cast because annotations cannot be applied to expressions:


public class Container<E>
{
   private E[] elements
   // ...
   @SuppressWarnings("unchecked")
   public Container<E>(int size)
   {
      // ...
      elements = (E[]) new Object[size];
   }
   // ...
}

Be careful when suppressing an unchecked warning. First prove that a ClassCastException cannot be thrown and then provide the appropriate @SuppressWarnings("unchecked") annotation.

In conclusion

Like generics, typesafe enums did much to improve the safety of Java programs, while annotations were aimed at increasing developer productivity. This article has been a brief tour of essential use cases for each feature. The next article in this series will introduce the remaining language features introduced in JDK 5 -- namely, autoboxing and unboxing, the enhanced for loop, static imports, varargs, and covariant return types.

1 2 Page 2
Page 2 of 2