Level: Introductory Dan Becker (beckerdo@us.ibm.com)IBM
01 May 1998 Most people think of reflection as the return of an image in a mirror or as serious thought. Developers using the Java programming language can include another meaning: a way to discover information about Java classes.
Editor's note: The Reflection API has been enhanced since the publication of this article; readers can consult the JDK documentation for java.lang.Class and java.lang.reflect.* for more information.
Java Version 1.1 or later allows a Java program to discover information
about the fields, methods, and constructors of loaded classes. Also, a Java
program can instantiate a class and operate on the underlying objects,
within security restrictions.
The Reflection Application Programming Interface (API) accommodates
applications that work solely on public members of an object (based on its
run-time class) or all the members declared by a given class. Although some
of this capability is allowed in Java 1.02, the important methods, such as
finding constructors or number and type of parameters, are available only
in Java 1.1 or later.
In this article, I explore the uses of reflection and develop an example
program that has many uses. The program can debug other programs, act as a
developer's quick reference tool, or explore the class hierarchy of an
object-oriented design.
Outlining the Class Hierarchy Using Java 1.02
At the simplest level, Java allows programs to inspect class information
through the Java class named Class, located in the package
java.lang. The most important methods are
getName()
and
getSuperclass(),
which allow you to print the name of the class
and find the super class (or parent) of the given class.
Let's begin by expanding on an example given by the grandfathers of the
Java Language, Ken Arnold and James Gosling, in the book
The Java
Programming Language
. The Java application shown below creates an instance
of itself and calls the
printType(...)
method for each command
line argument.
In this example, the
printType
method encapsulates all the
reflection code. It prints the name of the class, via the
getName()
method, and recursively calls itself on its super class, via the
getSuperclass()
method. This example is the simplest form of
reflection and was supported in Java 1.01 and 1.02. Notice that we don't
have to import the reflection package because the Java class Class
and its methods are located in the java.lang package.
public class Inspector {
// Methods
public static void main( String[] args ) {
Inspector inspector = new Inspector();
try {
for (int i = 0; i < args.length; i++ )
inspector.printType( Class.forName( args[ i ] ), 0 );
} catch ( ClassNotFoundException e ) {
System.err.print( e );
} /* endcatch */
}
public void printType( Class type, int depth ) {
// Print class name
System.out.println( "Class name " + depth + " is " + type.getName() );
// Recurse super classes
if ( type.getSuperclass() != null )
// true for all except java.lang.Object
printType( type.getSuperclass(), depth + 1 );
}
}
|
When you compile and run this program, you should see output similar to
the output shown below.
[c:\]java Inspector java.lang.Integer
Class name 0 is java.lang.Integer
Class name 1 is java.lang.Number
Class name 2 is java.lang.Object
[c:\]_
|
The name of the given class is followed by all of its parent classes,
ending with the mother of all Java classes, java.lang.Object. Note
that the output reflects the fully qualified class name of java.lang.Integer
since all Java class names are stored with their scope. There is no
notion of package importing in the Java run time.
If you do not get similar results, check your code and your Java
environment. Make any appropriate changes to product-similar results, or
the rest of the examples in this article will also fail.
Querying Interfaces Using Java 1.02
The first improvement that you can make to the program is to list the
interfaces a class implements as well as the classes it extends. This is
done with the
Class getInterfaces()
method, which is available
in Java 1.02 or later. The "Print Interfaces" loop added to the
printTypes(...)
method below recursively prints the types that a
class implements.
public void printType( Class type, int depth ) {
// Calculate indent and proper class label
StringBuffer indent = new StringBuffer();
for (int i = 0; i < depth; i++ ) indent.append( " " );
String[] labels = ( depth == 0 ? basic : extended );
System.out.print( indent + labels[ type.isInterface() ? 1 : 0 ] + " " );
// Print class name
System.out.println( type.getName() );
// Recurse super classes
if ( type.getSuperclass() != null ) // true for all except java.lang.Object
printType( type.getSuperclass(), depth + 1 );
// Print interfaces this class implements.
Class[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; i++ )
printType( interfaces[ i ], depth + 1 );
}
private final static String[]
basic = { "class", "interface" },
extended = { "extends", "implements" };
|
This example also contains "pretty printing" code that indents
the output and prints a label "class" or "interface"
to distinguish the two. Notice that within the first print statement I
have added the
isInterface()
method to perform this check. At
this point, all methods used in the program are available in Java 1.02.
An Improved Java 1.02 Reflection Program
The results from running this
code example are greatly improved from the first example. Not only do you
get a complete class hierarchy, but you also see the interfaces that a
class implements. One clue that this program was run under Java 1.1 or
later is that the java.lang.Number class implements java.io.Serializable,
a new interface introduced in the JDK 1.1.
[c:\]java Inspector java.lang.Integer
class java.lang.Integer
extends java.lang.Number
extends java.lang.Object
implements java.io.Serializable
[c:\]_ |
These are simple examples of reflection. Now let's add more advanced
introspection techniques to the printType() method. To accomplish this,
you need the new features of Java 1.1.
Using the Reflection Features of Java 1.1: Modifiers
The first bit of information you can obtain from the additional
reflection features is the scope and visibility of a given class. To do
this, you have to import the new java.lang.reflect package with an import
statement at the beginning of the program. You also use the new
getModifiers()
method of the Class class. This adds one line to the middle of the
printType
method, right before you print the class name. You are using a static
method from the new Modifier class to convert the modifiers of a class to
a String that can be printed by a program.
import java.lang.reflect.*;
public void printType( Class type, int depth ) {
...
// Print class modifiers
System.out.print( Modifier.toString( type.getModifiers() ) + " " );
// Print class name
System.out.println( type.getName() );
...
}
|
This modification results in the output shown below. Notice the classes
and interfaces now list their visibility (public) and other modifiers such
as synchronized, final, or abstract.
[c:\]java Inspector java.lang.Integer
class public final synchronized java.lang.Integer
extends public abstract synchronized java.lang.Number
extends public synchronized java.lang.Object
implements public interface java.io.Serializable
[c:\]_ |
Using the Reflection Features of Java 1.1: Fields
You can now make a great leap in the amount and quality of information
that is available from a Java class. You can query a class's constructors,
methods, and fields (another name for object or instance data), using
classes in the
java.lang.reflect
packag package. The classes
Constructor, Method, and Field all implement the Member
interface, and the methods for enumerating and printing these classes are
very similar. The following examples concentrate on the Fields class. A
complete example supporting Constructors and Methods is available on the
IBM Developer Connection Web site and on the set of CDs.
The next example adds a
printFields()
method to the
printType(...)
method. The parameters for this method are a String for pretty printing
and a class name that you would like to query.The definition of the
printFields()
method is shown below.
public void printType( Class type, int depth ) {
...
// Print class name
System.out.println( type.getName() );
// Print class fields
printFields( indent.toString(), type );
...
}
void printFields( String indent, Class type ) {
Field[] myFields = type.getDeclaredFields();
for( int i = 0; i
|
The first step is to query the fields of the class using the
getDeclaredFields()
method of the Class class. This method returns an array of all the fields
of a class. Then a loop goes through each Field in the array and prints
its modifiers, type, and name using the
getModifiers()
,
getType()
,
and
getName()
methods.
The
getType()
method returns a class. The name of this class
is not printed directly; rather, the class is passed to the
printArrayorClassType(...)
method. This is an important step because the field may be an array and
not merely a primitive or simple type.
If you use
getName()
to print the name of an array field, you
get a garbled name. So it is important to find the type a class represents
using the
isArray()
and
getComponentType()
methods of
the class Class as shown in the following example.
void printArrayorClassType( Class type ) {
if ( type.isArray() )
System.out.print( type.getComponentType().getName() + " [] " );
else
System.out.print( type.getName() + " " );
}
|
Notice that I queried the fields of the class using a
getDeclaredFields()
method. This returns all the fields declared in a class: public,
protected, and private. Typically, programs such as debuggers and heavy-duty
developer-class inspectors use this type of method to report on all
declared fields. There is also a method called
getFields()
that
returns all public fields of a class, including any field inherited from a
class's super type. Typically, applications such as Java beans and run-time
object inspectors use this method to report on the public signature of an
object. The example program in this article reports on all class
information using the former type of reflection.
When you compile and run this program on a class such as java.lang.Integer,
you can see five declared fields of the Integer class as well as one
declared field of the Number class.
[c:\]java simp java.lang.Integer
class public final synchronized java.lang.Integer
public static final int MIN_VALUE
public static final int MAX_VALUE
public static final java.lang.Class TYPE
private int value
private static final long serialVersionUID
extends public abstract synchronized java.lang.Number
|
Security Issues
As there are security concerns raised when running a Java applet
versus running a Java application, there are also security concerns when using
Java Reflection. Java Reflection uses two levels of checks to enforce
security when using this API:
- The Java class Class is the only source for instantiating the Field,
Method, and Constructor classes. Class delegates security checking to the
system security manager, which contains the
checkMemberAccess(Class,int)
method on a class-by-class basis. This methods throws a
SecurityException if access is not allowed.
- After satisfying the checks in step one, any code may query the reflected
member for its identifying information. However, standard Java language
checks are enforced when reflected members are used to operate on the
underlying members of objects; that is, to get or set field values, to
invoke methods, or to create and initialize new objects. There is no
notion of privileged code and no means to override the standard language
access control checks.
So, in the class inspector example, you are allowed to query the
constructors, methods, and fields of any class. But you might run into
IllegalAccessExceptions if you attempt to do one of the following:
- Use the Field class to get or set a field value
- Use the Method class to invoke a method
- Use the Constructor class to create and initialize a new instance of
a class
These are points to remember if you wish to go beyond
inspecting classes to creating and manipulating classes.
Conclusion
If you've followed this article closely, you should now understand the
workings of the entire Java reflection programming interface. It is now
entirely possible to inspect and act on Java classes on the fly. This is
important for creating programs that are extensible at run time. For
example, given an array of pixels and a class name (let's say it's an
image processing filter), one could write a program to search for the class
by name, load it, look for the right method, and run the class method on
the array of pixels. Users could add new image processing filters simply
by providing a new class file. There is no need to buy a new program to
support updates; simply add them yourself. Now that is a powerful and
flexible program!
About the author  | |  | Dan Becker works in the IBM Software Division in Austin, Texas. He is responsible for the
audio subsystem on various IBM implementations of the Java Sound API. Before that, Dan
worked on porting previous Java Virtual Machines, the multimedia plugins for Netscape
Navigator for OS/2, OpenDoc, and the Multimedia parts for OS/2 Warp, Version 4.0. Dan can
be reached at beckerdo@us.ibm.com or at his
public Web page at www.io.com/~beckerdo.
|
Rate this page
|