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.
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 |
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.
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.
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.
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.
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code | j-cwt11085.zip | 12 KB | HTTP |
Information about download methods
Learn
- The Classworking toolkit series (developerWorks, Dennis Sosnoski): Read the complete set of articles.
- "Tutorial: Introduction to generic types in JDK 5.0" (developerWorks, Brian Goetz, December 2004): Find out how to use
generic types in your code.
-
Java theory and practice: "Generic gotchas" (developerWorks, Brian Goetz, January 2005): Once you get past the basics,
see the pitfalls of generics.
- "Generics in
the Java Programming Language" (Sun Microsystems, Gilad Bracha): Tutorial by the principal architect of generic
type support in the Java language.
- Java
programming dynamics series (developerWorks, Dennis Sosnoski): Background on reflection, bytecode,
and classworking.
- JSR 14: Developed the
specification for generics, including the changes to the Java Language Specification.
- The Java technology
zone: Find articles about every aspect of Java programming.
Get products and technologies
- J2SE 5.0: Download J2SE 5.0
from the Sun Developer Network.
- JiBX XML data binding: The author's fast and flexible XML data binding framework.
Discuss
- developerWorks blogs: Get involved in the developerWorks community.

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.





