Reflection on Tiger Reflection on Tiger

by Michael Nascimento Santos
03/08/2004


Contents
New Kids on the Hierarchy
   Reflections on Metadata
   Working with Generics
Changes to the "Old" API
Conclusion

Much has been written about the new language features in J2SE 1.5, Tiger. Many, such as the introduction of enums, generics, and metadata, involve changing bytecode. These additions to the language require modifications to existing APIs. One of the APIs most radically affected by these changes is the Reflection API. Reflection has existed since JDK 1.1, and many frameworks, libraries, and utilities have been built upon it. This article examines the modifications to the Reflection API that are now available to the public as part of JDK 1.5 beta 1, and shows how you can take advantage of them in your code.

New Kids on the Hierarchy

A few interfaces and exceptions have been added to java.lang.reflect, as well as to java.lang. Some "old classes" -- such as Class, Method, Constructor, and others -- have been retrofitted to implement them. In this section we will look at these added interfaces.

Reflections on Metadata

The AnnotatedElement interface is implemented by any class that represents an element that may contain annotations (also known as metadata), as specified by JSR-175. The Class, Constructor, Field, Method, and Package each have been modified to implement AnnotatedElement. The interface specifies methods to retrieve all annotations, annotations by type, annotations declared only in a particular class (i.e., not including superclasses), and also a method to query whether or not a class contains an annotation of a specific type. The following code snippets demonstrate the use of these methods. Note that you must have some knowledge about how annotations work to fully understand this example.

First, create a custom annotation that we will later examine with reflection.

package net.java.reflection;

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

@Retention(RUNTIME)
public @interface SampleAnnotation {
   public String value();
}

Next, create a class AnnotatedElementTest and mark it and an instance method aMethod() as being annotated with the SampleAnnotation class you just created:

@SampleAnnotation("AnnotatedElementTest") 

public class AnnotatedElementTest {
   @SampleAnnotation("aMethod")
   @Deprecated

   public void aMethod() {
   }
//...
}

You can detect and get a handle to the Class annotations by creating a Class object and invoking the methods is AnnotationPresent() and getAnnotations(). You can repeat the process for the annotated method. The getAnnotations() method is used to retrieve all of the annotations this method has that are retained until runtime. When you compile and run AnnotatedElementTest, you will notice that @Deprecated is not shown. That is because it's only part of the source code and it's not present, even in the class file. Here's the listing for AnnotatedElementTest.

package net.java.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@SampleAnnotation("AnnotatedElementTest")

public class AnnotatedElementTest {

   @SampleAnnotation("aMethod")
   @Deprecated

   public void aMethod() {
   }

   public static void main(String[] args) throws Exception {
      Class<AnnotatedElementTest> clazz = AnnotatedElementTest.class;
      System.out.println(clazz.isAnnotationPresent(SampleAnnotation.class));
      System.out.println(clazz.getAnnotation(SampleAnnotation.class));
      Method method = clazz.getMethod("aMethod");
      for (Annotation a : method.getAnnotations()) {
         System.out.println(a);
      }
   }
}
Working with Generics

The following additions to the Reflection APIs allow you to obtain information about generics.

Here is an example of how to use some of these classes and interfaces related to generics.

package net.java.reflection;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Map;

// This class declaration defines a type variable T
public class GenericsTest<T extends Thread> {

   // This method is used in the reflection example below
   public void aMethod(Class<? extends T> clazz1, 
                       Class<T> clazz2, T[] ts) {
   }

   // Prints information about a type variable

   private static void print(TypeVariable v) {
      System.out.println("Type variable");
      System.out.println("Name: " + v.getName());
      System.out.println("Declaration: " + 
                         v.getGenericDeclaration());
      System.out.println("Bounds:");
      for (Type t : v.getBounds()) {
         print(t);
      }
   }
   // Prints information about a wildcard type
   private static void print(WildcardType wt) {
      System.out.println("Wildcard type");
      System.out.println("Lower bounds:");
      for (Type b : wt.getLowerBounds()) {
         print(b);
      }

      System.out.println("Upper bounds:");
      for (Type b : wt.getUpperBounds()) {
         print(b);
      }
   }

   // Prints information about a parameterized type
   private static void print(ParameterizedType pt) {
      System.out.println("Parameterized type");
      System.out.println("Owner: " + pt.getOwnerType());
      System.out.println("Raw type: " + pt.getRawType());
      
      for (Type actualType : pt.getActualTypeArguments()) {
         print(actualType);
      }
   }

   // Prints information about a generic array type
   private static void print(GenericArrayType gat) {
      System.out.println("Generic array type");
      System.out.println("Type of array: ");
      print(gat.getGenericComponentType());
   }

   /**
    * Prints information about a type. The nested 
    * if/else-if chain calls the
    * appropriate overloaded print method for the 
    * type. If t is just a Class,
    * we print it directly.
    */

   private static void print(Type t) {
      if (t instanceof TypeVariable) {
         print((TypeVariable)t);
      } else if (t instanceof WildcardType) {
         print((WildcardType)t);
      } else if (t instanceof ParameterizedType) {
         print((ParameterizedType)t);
      } else if (t instanceof GenericArrayType) {
         print((GenericArrayType)t);
      } else {
         System.out.println(t);
      }
   }

   public static void main(String[] args) throws Exception {
      // Some classes we are going to play with
      Class[] classes = new Class[] {Class.class, Map.class,
                                     GenericsTest.class};

      // Iterate the array for each class instance...
      for (Class clazz : classes) {
         // Prints its name and ...
         System.out.println("Class: " + clazz);

         // Iterate for each type variable defined by this class
         for (TypeVariable v : clazz.getTypeParameters()) {
            print(v);
         }

         System.out.println();
      }
      System.out.println("Reflective information " + 
                         "about the parameters of aMethod");
      // Iterate for each method...
      for (Method method : GenericsTest.class.getDeclaredMethods()) {
         // Until we find aMethod
         if (method.getName().equals("aMethod")) {
            // Then, go over all parameters ...
            for (Type t : method.getGenericParameterTypes()) {
               System.out.println("Parameter:");
               // And print reflexive information about them
               print(t);
               System.out.println();
            }
            break;
         }
      }
   }
}

Changes to the "Old" API

One thing most people erroneously assume is that they will be able to extract information about the actual binding of a type variable once they have a variable of a parameterized type. For example, if there is a method that accepts as a parameter that is declared as Collection<E>, inside of the method it is impossible to know what type actually is being used for E. That happens because generics are implemented in Java using erasure, which means that the actual type information is lost. There is no way to find what type was actually used to represent a type variable, even if you think that intentionally adding a method with this information would be possible. An example is shown below:

public class GenericClass<E> {
   // some code
   public Class<E> getElementType() {
      return E.class;
   }
   //more code
}

This code doesn't compile, since information about E is defined in runtime and erasured, while a reference to .class is statically resolved. That said, let's consider which changes have been made to the "old classes" that have been part of the API before Tiger:

Conclusion

Significant changes have been made to Java in order to make it more powerful and expressive. However, the side effect is the increased difficulty in using some Java APIs, especially Reflection, which must provide support for nearly all enhancements that require changes to bytecode. Luckily, as developers use the new API and have more resources available about the changes, such as this article, there will be more pros than cons.

Michael Nascimento Santos is a seasoned developer with more than 8 years of experience with the Java platform, from J2ME to J2EE, and over 14 years of pratical programming experience.


 Feed java.net RSS Feeds