TOC
BACK
FORWARD
HOME

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.

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.