Java 1.1 Unleashed
- 35 -
Java Reflection
by Mike Fletcher
IN THIS CHAPTER
- An Overview of the Reflection Package
- A Reflection Example
This chapter covers the new Reflection API introduced in Java 1.1. The java.lang.reflect
package contains classes that allow Java code to examine classes and interfaces dynamically.
After a quick overview of the API (refer to Chapter 18, "The Reflection Package,"
for more detailed coverage of the package), this chapter presents a sample applet
that uses the reflection classes to print out information about an arbitrary class.
An Overview of the Reflection Package
The Reflection API allows Java code to examine classes and objects at run time.
The new reflection classes allow you to call another class's methods dynamically
at run time. With the reflection classes, you can also examine an instance's fields
and change the fields' contents. The reflection classes also enable you to create
new class and array instances on the fly. All this new functionality is, of course,
limited by the Java security model and the Java language access specifiers (for example,
private and protected). The new Reflection API is intended to provide
the functionality required by the JavaBeans package to access the public
methods and fields of arbitrary classes at run time. The Reflection API also provides
applications such as debuggers with access to the declared members of a class.
In addition to the new reflection classes, some additions have been made to the
java.lang package. Byte and Short classes, analogous to
the Integer class, have been added to serve as wrappers for the corresponding
primitive types. New instances of the Class class, named java.lang.Type.TYPE
(where Type is Boolean, Character, Byte, Short,
Integer, Long, Float, Double, or Void),
have been added to represent primitive field, parameter, and return value types.
The java.lang.reflect API
The Reflection API consists of the java.lang.Class class and the java.lang.reflect
classes: Field, Method, Constructor, Array, and
Modifier. The first three of these classes represent the corresponding members
of classes and interfaces. The Array class provides methods for creating,
accessing, and modifying arrays. The last class provides methods for decoding modifiers
(such as static and public) returned for classes and their components.
All these classes allow widening conversions when appropriate (for example, an int
will be converted to a long), but throw an IllegalArgumentException
if the value has to be narrowed (for example, the classes do not convert a double
to a float).
The Class Class
The Class class, which remains in the java.lang package for
backwards compatibility, provides running code with a representation of Java types.
In Java 1.1, the Class class provides the same functionality as it did in
Java 1.0, with the following additions:
- Methods to determine whether an object represents an array or a primitive type.
- Methods that return a representation of the modifiers for the class type.
- Methods that return objects representing the fields, methods, and constructors,
as well as the component type for arrays. Two versions are available for each type:
One returns only the members declared specifically in the class, and the other returns
all members of the class, including inherited members.
- Methods that determine whether a class (or interface) is a superclass of a given
class (or interface), as well as methods that determine whether an object is an instance
of a type or implements an interface.
There are no public constructors for the Class class. The virtual
machine constructs Class objects when classes are loaded. References to
a Class object for a given class can be obtained with the forName()
method.
The Member Interface
The Member interface defines shared functionality implemented by the
Field, Method, and Constructor classes. It defines methods
to get information such as the name, declaring class, and modifiers for type members.
Two static constants, PUBLIC and DECLARED, are used by the security
manager to determine whether code can access members.
The Field Class
Field instances give access to one field of a type. A Field
object is created by the virtual machine when the getField(), getFields(),
getDeclaredField(), or getDeclaredFields() method of Class
is called. Both instance variables and class (static) variables can be represented
by a Field object.
In addition to the methods declared by the Member interface, the Field
class defines methods to retrieve and set the contents of a field. A generic get()
method is provided that takes an Object reference as a parameter and returns
an Object representing the value of the field. Specific methods are provided
to return a value of the appropriate primitive type (or throw an IllegalArgumentException
if the value represented by the Field object is not of that type). Similarly,
a generic set() method and other type-specific methods are provided to set
the contents of the field.
The Method Class
Just as the Field class represents the fields of a type, the Method
class represents the methods of a class. References to Method objects can
be obtained with the getMethod(), getDeclaredMethod(), getMethods(),
and getDeclaredMethods() members of the Class class. The Method
class implements the Member interface. The Method class provides
methods to determine the return type, parameters, and exceptions thrown by the represented
method. These are returned as references to Class objects for the first
two methods, and arrays of Class objects for the last two methods.
The invoke() method provides a way to call the method represented by
the Method object. It takes as parameters the Object on which to
call the method and an Object array representing the parameters. For class
methods, the first parameter can be omitted. If you have problems with the arguments
(for example, the first Object is not of the correct type, or one of the
parameters is not the correct type), an IllegalArgumentException is thrown.
If an exception is thrown by the method, it is caught and an InvocationTargetException
is thrown with the original exception (which can be retrieved with the getTargetException()
method).
The Constructor Class
The Constructor class represents a constructor for a class. A Constructor
instance can be used to create a new instantiation of the declaring class. As with
the Method and Field classes, instances can be created only by
the virtual machine and are retrieved with the Class class's getConstructor()
and related methods.
Methods are provided to return the parameter types and exceptions thrown by the
constructor. A new instance can be obtained by calling the newInstance()
method. As does the Method.invoke() method, the newInstance() method
throws an IllegalArgumentException if there is a problem with the type of
a parameter. An InvocationTargetException is thrown if the constructor throws
an exception.
The Array Class
The Array class provides a way of dynamically creating new arrays at
run time. Two methods, both with the name newInstance(), return a new array
with a component type specified by the first parameter. One version takes a single
int as the second parameter to specify the length of the array, the other
takes an int[] for creating multidimensional arrays. A getLength()
method takes an array reference as a parameter and returns the length of the array.
Methods similar to those of the Field class are provided by the Array
class to get and set array components. The get() method as well as the type-specific
versions of get() take an array reference and the index to retrieve as parameters.
The set() method and its type-specific friends take an additional argument,
which is the value to set the element.
The Modifier Class
The Modifier class is a convenience class that decodes the Java language
modifiers returned by the other Reflection API classes. All the Modifier
methods are static. Classes that implement the Member interface define a
getModifiers() method, which returns an int. This integer can be
passed to the methods of the Modifier class to determine which modifiers
are present. Methods such as isPublic() are provided for all the Java modifiers:
public, private, protected, static, final,
synchronized, volatile, transient, native, interface,
and abstract. A toString() method is also provided; it returns
a space-separated list of the modifiers represented by the int parameter.
The InvocationTargetException Exception
The InvocationTargetException exception is used to represent an exception
thrown by an underlying method or constructor called by the Method.invoke()
or Constructor.newInstance() method. Either of these methods catches any
exceptions thrown when the actual code is called. If an exception does occur, an
InvocationTargetException is created with the exception that occurred, and
this new exception is thrown. Two constructors are provided for this exception: One
takes only an object extending java.lang.Throwable, and the other takes
a Throwable and a message.
Security Concerns
Access to classes using the Reflection API is controlled by the security manager
and by the standard Java language access controls. Before an instance representing
a member (Field, Method, or Constructor) is returned,
the SecurityManager.checkMemberAccess() method is called. If this check
does not throw a SecurityException, access is granted, with the caveat of
the standard checks for private and protected done by the virtual
machine.
The access policy for applications is that code can access members for any class
to which it can link within the standard language constraints. The JDK AppletSecurity
class allows public members of public classes to access the public members of code
loaded by the same loader; it also allows access to public system classes (classes
that loaded from local disk somewhere in the CLASSPATH); the AppletSecurity
class also allows access to nonpublic members of classes loaded by the same class
loader. Code that has been signed by a trusted entity can access all members of system
classes. In all cases, standard Java access controls are applied. For example, even
though code can get a reference to a protected field, the code cannot get or set
the value; it can only find out information about the member such as its declaring
class and its name.
A Reflection Example
To show how the Reflection API can be used, this part of the chapter develops
a simple applet that displays information about a given class. The classInfo
constructor takes two parameters: the Class to print the information to,
and a java.awt.TextArea to display the information. A command-line version
can easily be created by replacing the TextArea with a PrintWriter
and converting the appendText() calls to print(). The final code
for the finished classInfo example and a driver applet that shows how to
use it is located on the accompanying CD-ROM.
Let's start with the overall skeleton for the class. Listing 35.1 shows the constructor
and a convenience method called smartTypePrint(). The constructor copies
its parameters into the corresponding instance variables. The smartTypePrint()
method determines whether the Class parameter represents an array type and
appends [] to the name if appropriate. The last three methods shown in Listing
35.1 are fleshed out in the next sections.
Listing 35.1. The classInfo skeleton.
import java.lang.reflect.*;
import java.io.*;
public class classInfo {
protected Class myClass;
protected java.awt.TextArea os;
// Constructor
public classInfo( Class c, java.awt.TextArea a ) {
myClass = c;
os = a;
}
public String smartTypePrint( Class c ) {
if( c.isArray( ) ) {
return new String( smartTypePrint( c.getComponentType() ) + "[]" );
} else {
return c.getName();
}
}
public void generalInfo() { ... }
public void methodInfo() { ... }
public void fieldInfo() { ... }
}
Class Name, Superclass, and Other Information
The generalInfo() method prints out information about the class in question.
The general information it prints includes the class's name, whether it is a class
or an interface, and any modifiers. Listing 35.2 shows the generalInfo()
method.
Listing 35.2. The generalInfo() method.
public void generalInfo( ) {
os.appendText( "Name: " + myClass.getName( ) + "\n" );
os.appendText( "Class/Interface: "
+ (myClass.isInterface() ? "Interface" : "Class") );
os.appendText( "\tSuperclass: " + myClass.getSuperclass().getName()
+ "\n" );
os.appendText( "Primitive type: " + myClass.isPrimitive() + "\n" );
os.appendText( "Interfaces: " );
Class interfaces[] = myClass.getInterfaces();
if( interfaces.length != 0 ) {
for( int i = 0; i < interfaces.length; i++ ) {
if( (i % 3) == 4) {
os.appendText( "\n\t" );
}
os.appendText( interfaces[ i ].getName() + " " );
}
os.appendText( "\n" );
} else {
os.appendText( "None\n" );
}
os.appendText( "Class Modifiers: "
+ Modifier.toString( myClass.getModifiers() ) + "\n" );
}
Fields
The fieldInfo() method prints each of the fields declared by the class
(see Listing 35.3). Inherited fields are ignored, although they can be obtained by
using the getFields() method instead of getDeclaredFields(). The
modifiers and type of the field are printed, followed by the name of the field. Although
the Field class provides a toString() method that provides the
same information, we'll do it ourselves here for the sheer fun of it.
Listing 35.3. The fieldInfo() method.
public void fieldInfo( ) {
Field fields[] = null;
os.appendText( "Fields:\n" );
try {
fields = myClass.getDeclaredFields();
} catch( SecurityException e ) {
os.appendText( "Error getting fields: "
+ e.getMessage() + "\n" );
return;
}
if( fields != null ) {
if( fields.length != 0 ) {
for( int i = 0; i < fields.length; i++ ) {
os.appendText( "\t" + Modifier.toString(
fields[i].getModifiers() ) );
os.appendText( " " + smartTypePrint( fields[i].getType() ) + " " );
os.appendText( fields[ i ].getName() + "\n" );
}
} else {
os.appendText( "\tNone\n" );
}
}
os.appendText( "\n" );
}
Constructors
Next, the constructorInfo() method prints out any constructors for the
class (see Listing 35.4). The modifiers for each constructor are printed, followed
by any parameters the constructor takes. If the constructor throws any exceptions,
these are printed next.
Listing 35.4. The constructorInfo() method.
public void constructorInfo( ) {
Constructor constructors[] = null;
os.appendText( "Constructors:\n" );
try {
constructors = myClass.getDeclaredConstructors();
} catch( SecurityException e ) {
os.appendText( "Error getting constructors: " + e + "\n" );
return;
}
if( constructors != null ) {
Class a[] = null;
if( constructors.length != 0 ) {
for( int i = 0; i < constructors.length; i++ ) {
os.appendText( " " + constructors[ i ].getName()
+ "\n ----------\n" );
os.appendText( "\tModifiers: " +
Modifier.toString( constructors[i].getModifiers() ) + "\n" );
a = constructors[ i ].getParameterTypes();
if( a.length != 0 ) {
os.appendText( "\tParameters (" + a.length + "): " );
for( int j = 0; j < a.length; j++ ) {
if( (j % 8) == 7 ) {
os.appendText( "\n\t\t" );
}
os.appendText( smartTypePrint( a[j] ) + " " );
}
} else {
os.appendText( "\tParameters: none" );
}
os.appendText( "\n" );
a = constructors[ i ].getExceptionTypes();
if( a.length != 0 ) {
os.appendText( "\tExceptions (" + a.length + "): " );
for( int j = 0; j < a.length; j++ ) {
if( (j % 8) == 7 ) {
os.appendText( "\n\t\t" );
}
os.appendText( smartTypePrint( a[j] ) + " " );
}
} else {
os.appendText( "\tExceptions: none" );
}
os.appendText( "\n\n" );
}
} else {
os.appendText( "None\n" );
}
}
}
Methods
The last method, methodInfo(), looks remarkably similar to the constructorInfo()
method (see Listing 35.5). The methodInfo() method also makes use of the
getReturnType() method to determine the return type of the method in question.
Listing 35.5. The methodInfo() method.
public void methodInfo( ) {
Method methods[] = null;
os.appendText( "Methods:\n" );
try {
methods = myClass.getDeclaredMethods();
} catch( SecurityException e ) {
os.appendText( "Error getting methods: " + e + "\n" );
return;
}
if( methods != null ) {
Class a[] = null;
if( methods.length != 0 ) {
for( int i = 0; i < methods.length; i++ ) {
os.appendText( " " + methods[ i ].getName()
+ "\n ----------\n" );
os.appendText( "\tModifiers: " +
Modifier.toString( methods[i].getModifiers() ) + "\n" );
os.appendText( "\tReturns: " +
smartTypePrint( methods[i].getReturnType() ) + "\n" );
a = methods[ i ].getParameterTypes();
if( a.length != 0 ) {
os.appendText( "\tParameters (" + a.length + "): " );
for( int j = 0; j < a.length; j++ ) {
if( (j % 8) == 7 ) {
os.appendText( "\n\t\t" );
}
os.appendText( smartTypePrint( a[j] ) + " " );
}
} else {
os.appendText( "\tParameters: none" );
}
os.appendText( "\n" );
a = methods[ i ].getExceptionTypes();
if( a.length != 0 ) {
os.appendText( "\tExceptions (" + a.length + "): " );
for( int j = 0; j < a.length; j++ ) {
if( (j % 8) == 7 ) {
os.appendText( "\n\t\t" );
}
os.appendText( smartTypePrint( a[j] ) + " " );
}
} else {
os.appendText( "\tExceptions: none" );
}
os.appendText( "\n\n" );
}
} else {
os.appendText( "None\n" );
}
}
}
User Interface
The user interface for the applet is very simple, as shown in Listing 35.6. It
consists of a TextField to allow the class name to be entered, a Button
to click to print the information, and the TextArea in which to display
the information.
When the button is clicked, the applet's action() method tries to get
a Class reference for the class named in the field. If it retrieves a Class,
it creates a classInfo object with the TextArea for output and
calls each of the information methods. Figure 35.1 shows the classInfoApplet
in action.
Figure 35.1.
The classInfoApplet.
Listing 35.6. The classInfoApplet class.
import java.applet.*;
import java.awt.*;
public class classInfoApplet
extends Applet
{
TextField className; // For entering class name
Button goButton; // Press button to do lookup
TextArea infoArea; // Area to print output
// init -- Create GUI.
public void init( ) {
setLayout( new BorderLayout( ) );
Panel p = new Panel();
p.setLayout( new FlowLayout( ) );
className = new TextField( 40 );
p.add( className );
goButton = new Button( "Print Info" );
p.add( goButton );
add( "North", p );
infoArea = new TextArea( 80, 80 );
add( "Center", infoArea );
}
// action -- Dispatch events
public boolean action( Event ev, Object arg ) {
// If the target is our button
if( ev.target == goButton ) {
Class c = null;
String name = className.getText(); // Get class name from field
// Let user know what we're up to
showStatus( "Trying class '" + name + "'" );
// Try and get a Class object for that name
try {
c = java.lang.Class.forName( name );
} catch( ClassNotFoundException e ) {
// Gripe if class wasn't found
showStatus( "Couldn't load class: " + e.getMessage() );
return true;
}
if( c == null ) {
// Paranoia check that we got a valid class object.
showStatus( "Class is null??" );
} else {
infoArea.setText( "" ); // Clear out text area
// Make a classInfo object with the Class and TextArea
classInfo ci = new classInfo( c, infoArea );
// Print information
ci.generalInfo();
ci.fieldInfo();
ci.constructorInfo();
ci.methodInfo();
// Let user know we're done.
showStatus( "Info for '" + name + "' done." );
}
return true; // Return that we handled event
}
// Let superclass handle event otherwise
return super.action( ev, arg );
}
}
Summary
You should now have an understanding of what the Reflection API can do and how
to use it. This new API provides useful functionality of interest to those who write
utilities such as debuggers and object browsers, and to those who use the JavaBeans
facilities.
|