Skip to main content

skip to main content

developerWorks  >  Java technology | XML  >

Classworking toolkit: Reflecting generics

Generics add type information that classworking tools can use

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Learn and share!

Exchange know-how with your peers -- try our new Pass It Along beta app


Rate this page

Help us improve this content


Level: Intermediate

Dennis Sosnoski (dms@sosnoski.com), Java and XML consultant, Sosnoski Software Solutions Inc.

08 Nov 2005

Java™ 5 extended the Java language type system to support parameterized types for classes, methods, and values. Parameterized types provide important compile-time advantages by enforcing proper type usage and eliminating casts from source code. Beyond these compile-time benefits, the type information can also be useful for classworking tools manipulating Java code. In this article, JiBX lead developer Dennis Sosnoski looks at how to use reflection to dig beneath the surface of parameterized types and reveal the full glory of Java 5 application data structures.

Many tools have been designed around the use of Java reflection, for purposes ranging from populating GUI components with data values to dynamically loading new features into running applications. Reflection is especially useful for analyzing data structures at run time, and many frameworks for converting between internal object structures and external forms (including XML, databases, and other persistence formats) have been built around reflection analysis of data structures.

One of the problems with using reflection for data structure analysis is that the standard Java collections classes (such as java.util.ArrayList) have always been "dead-ends" for reflection -- once you reached a collection class, there was no way to access more details of the data structure because there was no information about the type of items contained in the collection. Java 5 changed that with the added support for generic types and the conversion of all the collections classes to generic forms that support typing. Java 5 also extended the reflection APIs to provide access to generic type information at run time. Together, these changes allow reflection to dig deeper into data structures than ever before.

The code in the plain brown wrapper

Many articles have been written to cover using the Generics feature of Java 5 (including those linked in Resources). For this article, I assume you already know the basics of generics. We'll get started with some sample code and then jump right into how you access generic information at run time.

Ask the expert: Dennis Sosnoski on JVM and bytecode issues
For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.

As an example of working with generics, I'm going to use a data structure representing directories and files on a collection of paths. Listing 1 gives the code for the root class of this data structure. The PathDirectory class takes an array of path Strings as the constructor parameter. The constructor interprets each string as a directory path and builds a data structure to represent the files and child directories under that path. As each path is processed, the constructor adds the path and the data structure for that path to a pair collection.


Listing 1. Directory information set

public class PathDirectory implements Iterable<String>
{
    private final PairCollection<String, DirInfo> m_pathPairs;
    
    public PathDirectory(String[] paths) {
        m_pathPairs = new PairCollection<String, DirInfo>();
        for (String path : paths) {
            File file = new File(path);
            if (file.exists() && file.isDirectory()) {
                DirInfo info = new DirInfo(new File(path));
                m_pathPairs.add(path, info);
            }
        }
    }

    public PairCollection<String, DirInfo>.PairIterator iterator() {
        return m_pathPairs.iterator();
    }
    
    public static void main(String[] args) {
        PathDirectory inst = new PathDirectory(args);
        PairCollection<String, DirInfo>.PairIterator iter = inst.iterator();
        while (iter.hasNext()) {
            String path = iter.next();
            DirInfo info = iter.matching();
            System.out.println("Directory " + path + " has " +
                info.getFiles().size() + " files and " +
                info.getDirectories().size() + " child directories");
        }
    }
}

Listing 2 gives the PairCollection<T,U> code. This generic class handles pairs of values, with the type parameters giving the types of the items in the pairs. It provides an add() method to add a tuple to the collection, a clear() method to empty all tuples from the collection, and an iterator() method to return an iterator over the pairs in the collection. The inner PairIterator class implements the special iterator returned by the latter method, which defines an extra matching() method used to get the paired (second) value to the value returned by the standard next() method.


Listing 2. Generic pair collection

public class PairCollection<T,U> implements Iterable<T>
{
    // code assumes random access so force implementation class
	  private final ArrayList<T> m_tValues;
    private final ArrayList<U> m_uValues;
    
    public PairCollection() {
		    m_tValues = new ArrayList<T>();
        m_uValues = new ArrayList<U>();
    }
    
    public void add(T t, U u) {
        m_tValues.add(t);
        m_uValues.add(u);
    }
    
    public void clear() {
        m_tValues.clear();
        m_uValues.clear();
    }

    public PairIterator iterator() {
        return new PairIterator();
    }
    
    public class PairIterator implements Iterator<T>
    {
        private int m_offset;

        public boolean hasNext() {
            return m_offset < m_tValues.size();
        }

        public T next() {
            if (m_offset < m_tValues.size()) {
                return m_tValues.get(m_offset++);
            } else {
                throw new NoSuchElementException();
            }
        }

        public U matching() {
            if (m_offset > 0) {
                return m_uValues.get(m_offset-1);
            } else {
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}

PairCollection<T,U> uses generics internally for the actual collections holding the values. It implements the java.lang.Iterable interface with the first parameter type, allowing direct use in the new-style for loops to iterate over the first value of each pair. Unfortunately, when you use the new-style for loop, there's no way to access the actual iterator and hence no way to retrieve the second value of each pair. That's why the main() test method in Listing 1 uses a while loop instead of a new for loop.

Listing 3 gives the code for the pair of classes holding directory and file information. The DirInfo class uses typed java.util.List collections for the normal files and child directories of a directory. The constructor creates these collections as unmodifiable lists, making it safe to return them directly. The FileInfo class is simpler, just a holder for the file name and last modify date.


Listing 3. Directory and file data classes

public class DirInfo
{
    private final List<FileInfo> m_files;
    private final List<DirInfo> m_directories;
    private final Date m_lastModify;
    
    public DirInfo(File dir) {
        m_lastModify = new Date(dir.lastModified());
        File[] childs = dir.listFiles();
        List<FileInfo> files = new ArrayList<FileInfo>();
        List<DirInfo> dirs = new ArrayList<DirInfo>();
        for (int i = 0; i < childs.length; i++) {
            File child = childs[i];
            if (child.isDirectory()) {
                dirs.add(new DirInfo(child));
            } else if (child.isFile()) {
                files.add(new FileInfo(child));
            }
        }
        m_files = Collections.unmodifiableList(files);
        m_directories = Collections.unmodifiableList(dirs);
    }
    
    public List<DirInfo> getDirectories() {
        return m_directories;
    }
    public List<FileInfo> getFiles() {
        return m_files;
    }
    public Date getLastModify() {
        return m_lastModify;
    }
}

public class FileInfo
{
    private final String m_name;
    private final Date m_lastModify;
    
    public FileInfo(File file) {
        m_name = file.getName();
        m_lastModify = new Date(file.lastModified());
    }

    public Date getLastModify() {
        return m_lastModify;
    }
    public String getName() {
        return m_name;
    }
}

Listing 4 gives a sample run of the main() method from Listing 1:


Listing 4. Sample run

[dennis]$ java -cp . com.sosnoski.generics.PathDirectory 
  /home/dennis/bin /home/dennis/xtools /home/dennis/docs/business
Directory /home/dennis/bin has 31 files and 0 child directories
Directory /home/dennis/xtools has 0 files and 3 child directories
Directory /home/dennis/docs/business has 34 files and 34 child directories



Back to top


Generic reflections

Generics are implemented on the Java platform as a compile-time transformation. The compiler actually generates the same bytecode instructions as would be used for non-generic source code, inserting run-time casts to convert values to the appropriate type on each access. Despite this identical bytecode, the type parameter information is recorded in the class format using a new signature attribute. The JVM records this signature information when loading a class and makes it available at run time using reflection. In this section, I'll dig into the details of how the reflection API makes the type information available.

Type reflection interfaces

Accessing type parameter information through reflection is somewhat complex. As a starting point, you need to have a field with type information supplied (or some other typed alternative, such as a method parameter or return type). From the java.lang.reflect.Field instance for the field, you can then retrieve generic-specific information using the new getGenericType() method added by Java 5. This new method returns a java.lang.reflect.Type instance.

The only problem is that Type is an interface with no methods. Once you have an instance, you really need to check the subinterfaces that extend Type to see what you've got (and how to use it). The Javadocs list four subinterfaces, which I'll review in order. For convenience, I've reproduced the interface definitions in Listing 5. All are included in the java.lang.reflect package.


Listing 5. Subinterfaces of Type

interface GenericArrayType extends Type {
    Type getGenericComponentType();
}

interface ParameterizedType extends Type {
    Type[] getActualTypeArguments();
    Type getOwnerType();
    Type getRawType();
}

interface TypeVariable<D extends GenericDeclaration> extends Type {
    Type[] getBounds();
    D getGenericDeclaration();
    String getName();
}

interface WildcardType extends Type {
    Type[] getLowerBounds();
    Type[] getUpperBounds();
}

java.lang.reflect.GenericArrayType is the first subinterface. This subinterface provides information about array types, where the component type of the array is either parameterized or a type variable. There's only one method defined, getGenericComponentType(), which returns the array component Type.

java.lang.reflect.ParameterizedType is the second subinterface of Type. This one provides information about a generic type with specific type parameters. The interface defines three methods, the most interesting of which -- for the purposes of this article -- is the getActualTypeArguments() method. This method returns an array of (drum role) . . . still more Type instances. The returned Types represent the actual type arguments to the raw (unparameterized) type.

The third subinterface of Type is java.lang.reflect.TypeVariable<D extends GenericDeclaration>. This interface gives the details of a variable that represents a parameter type (such as the variable "D" within this type name). This interface defines three methods: getBounds(), which returns an array of (you guessed it) Type instances; getGenericDeclaration(), which returns an instance of the java.lang.reflect.GenericDeclaration interface corresponding to the type variable declaration; and getName(), which returns the name of the type variable as used in the source code. These methods all require further explanation, so I'll tackle them one by one.

The array of types returned by the getBounds() method define restrictions placed on the type of the variable. These restrictions are stated in the source code as clauses of the form extends B (where "B" is some type) added to template variables. Conveniently enough, java.lang.reflect.TypeVariable<D extends GenericDeclaration> itself gives an example of this form of upper bound definition -- java.lang.reflect.GenericDeclaration is an upper bound for the type parameter "D," meaning "D" must be a type that extends or implements GenericDeclaration.

The getGenericDeclaration() method provides a way to access the GenericDeclaration instance that declared TypeVariable. Three classes exist in the standard Java APIs that implement GenericDeclaration: java.lang.Class, java.lang.reflect.Constructor, and java.lang.reflect.Method. These three possibilities make sense because parameter types can only be declared on classes, constructors, and methods. The GenericDeclaration interface defines a single method, which returns the array of TypeVariables included in the declaration.

The getName() method just returns the name of the type variable, exactly as given in the source code.

java.lang.reflect.WildcardType is the fourth and last subinterface of Type. WildcardType just defines a pair of methods to return both lower and upper bounds for the wildcard type. Earlier, I gave an example of upper bounds; lower bounds are similar, but they are defined by specifying a type of which the supplied type must be a superinterface or superclass.

Reflections on an example

The reflection interfaces I described in the last section provide the hooks to decode generics information, but they can be a little difficult to figure out -- no matter where you start, everything seems to loop around and come back to java.lang.reflect.Type. To demonstrate how these work, I'll take an example from the Listing 1 code and reflect on that.

To start with, I'll try to access the type information for the m_pathPairs field of Listing 1. The Listing 6 code gets the generic type of that field, checks that the result is of the expected type, and then lists out the raw type and the actual type arguments for the parameterized type. The output from running the code is shown in bold at the end of Listing 6:


Listing 6. First cut reflection code

public static void main(String[] args) throws Exception {
    
    // get the basic information
    Field field =
        PathDirectory.class.getDeclaredField("m_pathPairs");
    Type gtype = field.getGenericType();
    if (gtype instanceof ParameterizedType) {
        
        // list the raw type information
        ParameterizedType ptype = (ParameterizedType)gtype;
        Type rtype = ptype.getRawType();
        System.out.println("rawType is instance of " +
            rtype.getClass().getName());
        System.out.println(" (" + rtype + ")");
        
        // list the actual type arguments
        Type[] targs = ptype.getActualTypeArguments();
        System.out.println("actual type arguments are:");
        for (int j = 0; j < targs.length; j++) {
            System.out.println(" instance of " +
                targs[j].getClass().getName() + ":");
            System.out.println("  (" + targs[j] + ")");
        }
    } else {
        System.out.println
            ("getGenericType is not a ParameterizedType!");
    }
}

rawType is instance of java.lang.Class
 (class com.sosnoski.generics.PairCollection)
actual type arguments are:
 instance of java.lang.Class:
  (class java.lang.String)
 instance of java.lang.Class:
  (class com.sosnoski.generics.DirInfo)

So far, so good. The m_pathPairs field is defined as being of type PairCollection<String, DirInfo>, which matches the type information accessed by reflection. Digging into the actual parameterized type definition gets tricky, though; the returned Type instance is a java.lang.Class object that does not implement any of the Type subinterfaces. Fortunately, the Java 5 Class<T> class itself provides a method to dig into the details of a generified class definition. The method is getTypeParameters(), which returns an array of TypeVariable<Class<T>>. In Listing 7, I've modified the Listing 6 code to use this method, with the results again shown in bold following the code:


Listing 7. Digging into a parameterized type

public static void main(String[] args) throws Exception {
    
    // get the basic information
    Field field =
        PathDirectory.class.getDeclaredField("m_pathPairs");
    ParameterizedType ptype =
        (ParameterizedType)field.getGenericType();
    Class rclas = (Class)ptype.getRawType();
    System.out.println("rawType is class " + rclas.getName());
    
    // list the type variables of the base class
    TypeVariable[] tvars = rclas.getTypeParameters();
    for (int i = 0; i < tvars.length; i++) {
        TypeVariable tvar = tvars[i];
        System.out.print(" Type variable " +
            tvar.getName() + " with upper bounds [");
        Type[] btypes = tvar.getBounds();
        for (int j = 0; j < btypes.length; j++) {
            if (j > 0) {
                System.out.print(" ");
            }
            System.out.print(btypes[j]);
        }
        System.out.println("]");
    }
    
    // list the actual type arguments
    Type[] targs = ptype.getActualTypeArguments();
    System.out.print("Actual type arguments are\n (");
    for (int j = 0; j < targs.length; j++) {
        if (j > 0) {
            System.out.print(" ");
        }
        Class tclas = (Class)targs[j];
        System.out.print(tclas.getName());
    }
    System.out.print(")");
}
rawType is class com.sosnoski.generics.PairCollection
 Type variable T with upper bounds [class java.lang.Object]
 Type variable U with upper bounds [class java.lang.Object]
Actual type arguments are
 (java.lang.String com.sosnoski.generics.DirInfo)

The Listing 7 results show the decoded structure. The actual type arguments can be matched to the type variables defined by the generified class. In the next section, I'll do just this as part of a recursive generic decoding method.



Back to top


Generic recursion

In the last section, I went through a quick run-through of the reflection methods used to access generics information. Now I'll use those methods to build a recursive processor to interpret generics. Listing 8 gives the code for this purpose:


Listing 8. Recursive generics analysis

public class Reflect
{
    private static HashSet<String> s_processed = new HashSet<String>();
    
    private static void describe(String lead, Field field) {
        
        // get base and generic types, check kind
        Class<?> btype = field.getType();
        Type gtype = field.getGenericType();
        if (gtype instanceof ParameterizedType) {
            
            // list basic parameterized type information
            ParameterizedType ptype = (ParameterizedType)gtype;
            System.out.println(lead + field.getName() +
                " is of parameterized type");
            System.out.println(lead + ' ' + btype.getName());
            
            // print list of actual types for parameters
            System.out.print(lead + " using types (");
            Type[] actuals = ptype.getActualTypeArguments();
            for (int i = 0; i < actuals.length; i++) {
                if (i > 0) {
                    System.out.print(" ");
                }
                Type actual = actuals[i];
                if (actual instanceof Class) {
                    System.out.print(((Class)actual).getName());
                } else {
                    System.out.print(actuals[i]);
                }
            }
            System.out.println(")");
            
            // analyze all parameter type classes
            for (int i = 0; i < actuals.length; i++) {
                Type actual = actuals[i];
                if (actual instanceof Class) {
                    analyze(lead, (Class)actual);
                }
            }
            
        } else if (gtype instanceof GenericArrayType) {
            
            // list array type and use component type
            System.out.println(lead + field.getName() +
                " is array type " + gtype);
            gtype = ((GenericArrayType)gtype).
                getGenericComponentType();
            
        } else {
            
            // just list basic information
            System.out.println(lead + field.getName() +
                " is of type " + btype.getName());
        }
        
        // analyze the base type of this field
        analyze(lead, btype);
    }
    
    private static void analyze(String lead, Class<?> clas) {
        
        // substitute component type in case of an array
        if (clas.isArray()) {
            clas = clas.getComponentType();
        }
        
        // make sure class should be expanded
        String name = clas.getName();
        if (!clas.isPrimitive() && !clas.isInterface() &&
            !name.startsWith("java.lang.") &&
            !s_processed.contains(name)) {
            
            // print introduction for class
            s_processed.add(name);
            System.out.println(lead + "Class " +
                clas.getName() + " details:");
            
            // process each field of class
            String indent = lead + ' ';
            Field[] fields = clas.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                if (!Modifier.isStatic(field.getModifiers())) {
                    describe(indent, field);
                }
            }
        }
    }
    
    public static void main(String[] args) throws Exception {
        analyze("", PathDirectory.class);
    }
}

The Listing 8 code uses a pair of mutually recursive methods for the actual analysis. The analyze() method takes a class as an argument, expanding that class by processing each field definition if it's appropriate. The describe() method prints a description of the type information for a particular field, calling analyze() for each class it encounters in the process. Each method also takes an argument giving the current leading indentation string, which gets an added space for each level of class nesting.

Listing 9 gives the output generated by using the Listing 8 code to analyze the full structure of the code from Listing 1, Listing 2, and Listing 3.


Listing 9. Analysis of generics sample code

Class com.sosnoski.generics.PathDirectory details:
 m_pathPairs is of parameterized type
  com.sosnoski.generics.PairCollection
  using types (java.lang.String com.sosnoski.generics.DirInfo)
 Class com.sosnoski.generics.DirInfo details:
  m_files is of parameterized type
   java.util.List
   using types (com.sosnoski.generics.FileInfo)
  Class com.sosnoski.generics.FileInfo details:
   m_name is of type java.lang.String
   m_lastModify is of type java.util.Date
   Class java.util.Date details:
    fastTime is of type long
    cdate is of type sun.util.calendar.BaseCalendar$Date
    Class sun.util.calendar.BaseCalendar$Date details:
     cachedYear is of type int
     cachedFixedDateJan1 is of type long
     cachedFixedDateNextJan1 is of type long
  m_directories is of parameterized type
   java.util.List
   using types (com.sosnoski.generics.DirInfo)
  m_lastModify is of type java.util.Date
 Class com.sosnoski.generics.PairCollection details:
  m_tValues is of parameterized type
   java.util.ArrayList
   using types (T)
  Class java.util.ArrayList details:
   elementData is array type E[]
   size is of type int
  m_uValues is of parameterized type
   java.util.ArrayList
   using types (U)

The Listing 9 output gives the basics of how generic types are parameterized for use, including the types of items specified for the m_files and m_directories lists in the DirInfo class. But when it gets to the PairCollection class (at the bottom), the field types are only given as variables. The reason this only shows variables for the field types is that the generic type information provided by reflection does not handle substitutions -- it's up to the user of the reflection code to handle the type substitutions within the generified class. It's not too difficult to do this, as you might guess from the Listing 9 output. Here the details of the m_tValues expansion show that the ArrayList is parameterized using the "T" type, and the nested ArrayList expansion shows that the elementData field is parameterized using the "E" type. To correctly associate these types in each instance, I'd need to track the actual types being substituted for the type variables (available from the java.lang.Class.getTypeParameters() method, as discussed earlier) at each stage of the expansion. In this case, that would mean substituting java.lang.String for "T" in the PairCollection expansion and for "E" in the m_tValues ArrayList expansion. Rather than hit double digits in the listing numbers, I'll leave the details of the changes to you.



Back to top


More generics to come

I've shown in this article how you can dig generic type information out of compiled classes at run time (at least the basics -- I ignored complications such as inner classes and some of the more convoluted constructions possible with generics).

As an example application, I'm planning to use generic type information to improve the default binding generator provided with my JiBX XML data binding framework. Right now the binding generator doesn't know what kind of content is present in a Java collection (or other untyped reference), so the generator just leaves it to the user to modify the generating binding and add the appropriate content; once the generics reflection code is added, the generator will instead be able to get type information directly from generics for users of Java 5.

But it's not always convenient to load classes into the JVM to access generic type information. In the case of JiBX, the most important part of its work with classes occurs when it's adding bytecode to the compiled class representations. For this work, JiBX uses a bytecode manipulation framework (BCEL in JiBX 1.X, changing to ASM in JiBX 2.0). Fortunately for JiBX, the ASM framework includes hooks for accessing this same type information while parsing the binary class representations and for adding the generic type information when generating new classes. Next month, I'll show how the ASM analysis approach compares to the reflection support I covered this month.




Back to top


Download

DescriptionNameSizeDownload method
Source codej-cwt11085.zip12 KBHTTP
Information about download methods


Resources

Learn

Get products and technologies

Discuss


About the author

Dennis Sosnoski

Dennis Sosnoski is the founder and lead consultant of Seattle-area Java technology consulting company Sosnoski Software Solutions, Inc., specialists in XML and Web services training and consulting. His professional software development experience spans over 30 years, with the last several years focused on server-side XML and Java technologies. Dennis is a frequent speaker at conferences nationwide, and a member of the JSR-222 (JAXB 2.0) and JSR-224 (JAX-RPC 2.0) expert groups. He's also the lead developer of the open source JiBX XML Data Binding framework built around Java classworking technology.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top