Reflection

The reflection library gives you a very rich and elaborate toolset to write programs that manipulate Java code dynamically. This feature is heavily used in JavaBeans, the component architecture for Java (see Volume 2 for more on JavaBeans). Using reflection, Java can support tools like the ones to which users of Visual Basic have grown accustomed. In particular, when new classes are added at design or run time, rapid application development tools can dynamically inquire about the capabilities of the classes that were added.

A program that can analyze the capabilities of classes is called reflective. The reflection mechanism is extremely powerful. As the next sections show, you can use it to

  • Analyze the capabilities of classes at run time;

  • Inspect objects at run time, for example, to write a single toString method that works for all classes;

  • Implement generic array manipulation code; and

  • Take advantage of Method objects that work just like function pointers in languages such as C++.

Reflection is a powerful and complex mechanism; however, it is of interest mainly to tool builders, not application programmers. If you are interested in programming applications rather than tools for other Java programmers, you can safely skip the remainder of this chapter and return to it later.

The Class Class

While your program is running, the Java runtime system always maintains what is called runtime type identification on all objects. This information keeps track of the class to which each object belongs. Runtime type information is used by the virtual machine to select the correct methods to execute.

However, you can also access this information by working with a special Java class. The class that holds this information is called, somewhat confusingly, Class. The getClass() method in the Object class returns an instance of Class type.

Employee e;
. . .
Class cl = e.getClass();

Just like an Employee object describes the properties of a particular employee, a Class object describes the properties of a particular class. Probably the most commonly used method of Class is getName. This returns the name of the class. For example, the statement

System.out.println(e.getClass().getName() + " " + e.getName());

prints

Employee Harry Hacker

if e is an employee, or

Manager Harry Hacker

if e is a manager.

You can also obtain a Class object corresponding to a string by using the static forName method.

String className = "java.util.Date";
Class cl = Class.forName(className);

You would use this method if the class name is stored in a string that varies at run time. This works if className is the name of a class or interface. Otherwise, the forName method throws a checked exception. See the sidebar "Catching Exceptions" on page 192 to see how to supply an exception handler whenever you use this method.

TIP

At startup, the class containing your main method is loaded. It loads all classes that it needs. Each of those loaded classes loads the classes that it needs, and so on. That can take a long time for a big application, frustrating the user. You can give users of your program the illusion of a faster start with the following trick. Make sure that the class containing the main method does not explicitly refer to other classes. First display a splash screen. Then manually force the loading of other classes by calling Class.forName.


A third method for obtaining an object of type Class is a convenient shorthand. If T is any Java type, then T.class is the matching class object. For example:

Class cl1 = Date.class; // if you import java.util.*;
Class cl2 = int.class;
Class cl3 = Double[].class;

Note that a Class object really describes a type, which may or may not be a class. For example, int is not a class, but int.class is nevertheless an object of type Class.

NOTE

As of JDK 5.0, the Class class is parameterized. For example, Employee.class is of type Class<Employee>. We are not dwelling on this issue because it would further complicate an already abstract concept. For most practical purposes, you can ignore the type parameter and work with the raw Class type. See Chapter 13 for more information on this issue.


CAUTION

For historical reasons, the getName method returns somewhat strange names for array types:

  • Double[].class.getName() returns "[Ljava.lang.Double;".

  • int[].class.getName() returns "[I".


The virtual machine manages a unique Class object for each type. Therefore, you can use the == operator to compare class objects, for example,

if (e.getClass() == Employee.class) . . .

Another example of a useful method is one that lets you create an instance of a class on the fly. This method is called, naturally enough, newInstance(). For example,

e.getClass().newInstance();

creates a new instance of the same class type as e. The newInstance method calls the default constructor (the one that takes no parameters) to initialize the newly created object. An exception is thrown if the class has no default constructor.

Using a combination of forName and newInstance lets you create an object from a class name stored in a string.

String s = "java.util.Date";
Object m = Class.forName(s).newInstance();

NOTE

If you need to provide parameters for the constructor of a class you want to create by name in this manner, then you can't use statements like the above. Instead, you must use the newInstance method in the Constructor class. (This is one of several classes in the java.lang.reflect package. We discuss reflection in the next section.)


C++ NOTE

The newInstance method corresponds to the idiom of a virtual constructor in C++. However, virtual constructors in C++ are not a language feature but just an idiom that needs to be supported by a specialized library. The Class class is similar to the type_info class in C++, and the getClass method is equivalent to the typeid operator. The Java Class is quite a bit more versatile than type_info, though. The C++ type_info can only reveal a string with the name of the type, not create new objects of that type.


Catching Exceptions

We will cover exception handling fully in Chapter 11, but in the meantime you will occasionally encounter methods that threaten to throw exceptions.

When an error occurs at run time, a program can "throw an exception." Throwing an exception is more flexible than terminating the program because you can provide a handler that "catches" the exception and deals with it.

If you don't provide a handler, the program still terminates and prints a message to the console, giving the type of the exception. You may already have seen exception reports when you accidentally used a null reference or overstepped the bounds of an array.

There are two kinds of exceptions: unchecked exceptions and checked exceptions. With checked exceptions, the compiler checks that you provide a handler. However, many common exceptions, such as accessing a null reference, are unchecked. The compiler does not check whether you provide a handler for these errors—after all, you should spend your mental energy on avoiding these mistakes rather than coding handlers for them.

But not all errors are avoidable. If an exception can occur despite your best efforts, then the compiler insists that you provide a handler. The Class.forName method is an example of a method that throws a checked exception. In Chapter 11, you will see several exception handling strategies. For now, we'll just show you the simplest handler implementation.

Place one or more methods that might throw checked exceptions inside a try block. Then provide the handler code in the catch clause.

try
{
   statements that might throw exceptions
}
catch(Exception e)
{
   handler action
}

Here is an example:

try
{  String name = . . .; // get class name
   Class cl = Class.forName(name); // might throw exception
   . . . // do something with cl
}
catch(Exception e)
{
   e.printStackTrace();
}

If the class name doesn't exist, the remainder of the code in the try block is skipped, and the program enters the catch clause. (Here, we print a stack trace by using the printStackTrace method of the Throwable class. Throwable is the superclass of the Exception class.) If none of the methods in the try block throw an exception, then the handler code in the catch clause is skipped.

You only need to supply an exception handler for checked exceptions. It is easy to find out which methods throw checked exceptions—the compiler will complain whenever you call a method that threatens to throw a checked exception and you don't supply a handler.


												
										

												
														java.lang.Class 1.0
												
												
												
										

  • static Class forName(String className)

    returns the Class object representing the class with name className.

  • Object newInstance()

    returns a new instance of this class.

												
										

												
														java.lang.reflect.Constructor 1.1
												
												
												
										

  • Object newInstance(Object[] args)

    constructs a new instance of the constructor's declaring class.

    Parameters:

    args

    the parameters supplied to the constructor. See the section on reflection for more information on how to supply parameters.


												
										

												
														java.lang.Throwable 1.0
												
												
												
												
												
										

  • void printStackTrace()

    prints the Throwable object and the stack trace to the standard error stream.

Using Reflection to Analyze the Capabilities of Classes

Here is a brief overview of the most important parts of the reflection mechanism for letting you examine the structure of a class.

The three classes Field, Method, and Constructor in the java.lang.reflect package describe the fields, methods, and constructors of a class, respectively. All three classes have a method called getName that returns the name of the item. The Field class has a method getType that returns an object, again of type Class, that describes the field type. The Method and Constructor classes have methods to report the types of the parameters, and the Method class also reports the return type. All three of these classes also have a method called getModifiers that returns an integer, with various bits turned on and off, that describes the modifiers used, such as public and static. You can then use the static methods in the Modifier class in the java.lang.reflect package to analyze the integer that getModifiers returns. Use methods like isPublic, isPrivate, or isFinal in the Modifier class to tell whether a method or constructor was public, private, or final. All you have to do is have the appropriate method in the Modifier class work on the integer that getModifiers returns. You can also use the Modifier.toString method to print the modifiers.

The getFields, getMethods, and getConstructors methods of the Class class return arrays of the public fields, methods, and constructors that the class supports. This includes public members of superclasses. The getdeclaredFields, geTDeclaredMethods, and geTDeclaredConstructors methods of the Class class return arrays consisting of all fields, operations, and constructors that are declared in the class. This includes private and protected members, but not members of superclasses.

Example 5-5 shows you how to print out all information about a class. The program prompts you for the name of a class and then writes out the signatures of all methods and constructors as well as the names of all data fields of a class. For example, if you enter

java.lang.Double

then the program prints:

class java.lang.Double extends java.lang.Number
{
   public java.lang.Double(java.lang.String);
   public java.lang.Double(double);
   public int hashCode();
   public int compareTo(java.lang.Object);
   public int compareTo(java.lang.Double);
   public boolean equals(java.lang.Object);
   public java.lang.String toString();
   public static java.lang.String toString(double);
   public static java.lang.Double valueOf(java.lang.String);
   public static boolean isNaN(double);
   public boolean isNaN();
   public static boolean isInfinite(double);
   public boolean isInfinite();
   public byte byteValue();
   public short shortValue();
   public int intValue();
   public long longValue();
   public float floatValue();
   public double doubleValue();
   public static double parseDouble(java.lang.String);
   public static native long doubleToLongBits(double);
   public static native long doubleToRawLongBits(double);
   public static native double longBitsToDouble(long);

   public static final double POSITIVE_INFINITY;
   public static final double NEGATIVE_INFINITY;
   public static final double NaN;
   public static final double MAX_VALUE;
   public static final double MIN_VALUE;
   public static final java.lang.Class TYPE;
   private double value;
   private static final long serialVersionUID;
}

What is remarkable about this program is that it can analyze any class that the Java interpreter can load, not just the classes that were available when the program was compiled. We use this program in the next chapter to peek inside the inner classes that the Java compiler generates automatically.

Example 5-5. ReflectionTest.java
  1. import java.util.*;
  2. import java.lang.reflect.*;
  3.
  4. public class ReflectionTest
  5. {
  6.    public static void main(String[] args)
  7.    {
  8.       // read class name from command-line args or user input
  9.       String name;
 10.       if (args.length > 0)
 11.          name = args[0];
 12.       else
 13.       {
 14.          Scanner in = new Scanner(System.in);
 15.          System.out.println("Enter class name (e.g. java.util.Date): ");
 16.          name = in.next();
 17.       }
 18.
 19.       try
 20.       {
 21.          // print class name and superclass name (if != Object)
 22.          Class cl = Class.forName(name);
 23.          Class supercl = cl.getSuperclass();
 24.          System.out.print("class " + name);
 25.          if (supercl != null && supercl != Object.class)
 26.             System.out.print(" extends " + supercl.getName());
 27.
 28.          System.out.print("\n{\n");
 29.          printConstructors(cl);
 30.          System.out.println();
 31.          printMethods(cl);
 32.          System.out.println();
 33.          printFields(cl);
 34.          System.out.println("}");
 35.       }
 36.       catch(ClassNotFoundException e) { e.printStackTrace(); }
 37.       System.exit(0);
 38.    }
 39.
 40.    /**
 41.       Prints all constructors of a class
 42.       @param cl a class
 43.     */
 44.    public static void printConstructors(Class cl)
 45.    {
 46.       Constructor[] constructors = cl.getDeclaredConstructors();
 47.
 48.       for (Constructor c : constructors)
 49.       {
 50.          String name = c.getName();
 51.          System.out.print("   " + Modifier.toString(c.getModifiers()));
 52.          System.out.print(" " + name + "(");
 53.
 54.          // print parameter types
 55.          Class[] paramTypes = c.getParameterTypes();
 56.          for (int j = 0; j < paramTypes.length; j++)
 57.          {
 58.             if (j > 0) System.out.print(", ");
 59.             System.out.print(paramTypes[j].getName());
 60.          }
 61.          System.out.println(");");
 62.       }
 63.    }
 64.
 65.    /**
 66.       Prints all methods of a class
 67.       @param cl a class
 68.     */
 69.    public static void printMethods(Class cl)
 70.    {
 71.       Method[] methods = cl.getDeclaredMethods();
 72.
 73.       for (Method m : methods)
 74.       {
 75.          Class retType = m.getReturnType();
 76.          String name = m.getName();
 77.
 78.          // print modifiers, return type and method name
 79.          System.out.print("   " + Modifier.toString(m.getModifiers()));
 80.          System.out.print(" " + retType.getName() + " " + name + "(");
 81.
 82.          // print parameter types
 83.          Class[] paramTypes = m.getParameterTypes();
 84.          for (int j = 0; j < paramTypes.length; j++)
 85.          {
 86.             if (j > 0) System.out.print(", ");
 87.             System.out.print(paramTypes[j].getName());
 88.          }
 89.          System.out.println(");");
 90.       }
 91.    }
 92.
 93.    /**
 94.       Prints all fields of a class
 95.       @param cl a class
 96.     */
 97.    public static void printFields(Class cl)
 98.    {
 99.       Field[] fields = cl.getDeclaredFields();
100.
101.       for (Field f : fields)
102.       {
103.          Class type = f.getType();
104.          String name = f.getName();
105.          System.out.print("   " + Modifier.toString(f.getModifiers()));
106.          System.out.println(" " + type.getName() + " " + name + ";");
107.       }
108.    }
109. }

												
										

												
														java.lang.Class 1.0
												
												
												
												
												
										

  • Field[] getFields() 1.1

  • Field[] getDeclaredFields() 1.1

    The getFields method returns an array containing Field objects for the public fields of this class or its superclasses. The geTDeclaredField method returns an array of Field objects for all fields of this class. The methods return an array of length 0 if there are no such fields or if the Class object represents a primitive or array type.

  • Method[] getMethods() 1.1

  • Method[] getDeclaredMethods() 1.1

    return an array containing Method objects: getMethods returns public methods and includes inherited methods; getdeclaredMethods returns all methods of this class or interface but does not include inherited methods.

  • Constructor[] getConstructors() 1.1

  • Constructor[] getDeclaredConstructors() 1.1

    return an array containing Constructor objects that give you all the public constructors (for getConstructors) or all constructors (for getdeclaredConstructors) of the class represented by this Class object.

												
										

												
														java.lang.reflect.Field 1.1
												
										

												
										

												
														java.lang.reflect.Method 1.1
												
												
												
										

												
										

												
														java.lang.reflect.Constructor 1.1
												
												
												
										

  • Class getDeclaringClass()

    returns the Class object for the class that defines this constructor, method, or field.

  • Class[] getExceptionTypes() (in Constructor and Method classes)

    returns an array of Class objects that represent the types of the exceptions thrown by the method.

  • int getModifiers()

    returns an integer that describes the modifiers of this constructor, method, or field. Use the methods in the Modifier class to analyze the return value.

  • String getName()

    returns a string that is the name of the constructor, method, or field.

  • Class[] getParameterTypes() (in Constructor and Method classes)

    returns an array of Class objects that represent the types of the parameters.

  • Class getReturnType() (in Method classes)

    returns a Class object that represents the return type.

												
										

												
														java.lang.reflect.Modifier 1.1
												
												
												
												
												
										

  • static String toString(int modifiers)

    returns a string with the modifiers that correspond to the bits set in modifiers.

  • static boolean isAbstract(int modifiers)

  • static boolean isFinal(int modifiers)

  • static boolean isInterface(int modifiers)

  • static boolean isNative(int modifiers)

  • static boolean isPrivate(int modifiers)

  • static boolean isProtected(int modifiers)

  • static boolean isPublic(int modifiers)

  • static boolean isStatic(int modifiers)

  • static boolean isStrict(int modifiers)

  • static boolean isSynchronized(int modifiers)

  • static boolean isVolatile(int modifiers)

    These methods test the bit in the modifiers value that corresponds to the modifier in the method name.

Using Reflection to Analyze Objects at Run Time

In the preceding section, we saw how we can find out the names and types of the data fields of any object:

  • Get the corresponding Class object.

  • Call getdeclaredFields on the Class object.

In this section, we go one step further and actually look at the contents of the data fields. Of course, it is easy to look at the contents of a specific field of an object whose name and type are known when you write a program. But reflection lets you look at fields of objects that were not known at compile time.

The key method to achieve this examination is the get method in the Field class. If f is an object of type Field (for example, one obtained from getdeclaredFields) and obj is an object of the class of which f is a field, then f.get(obj) returns an object whose value is the current value of the field of obj. This is all a bit abstract, so let's run through an example.

Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();
   // the class object representing Employee
Field f = cl.getDeclaredField("name");
   // the name field of the Employee class
Object v = f.get(harry);
   // the value of the name field of the harry object
   // i.e., the String object "Harry Hacker"

Actually, there is a problem with this code. Because the name field is a private field, the get method will throw an IllegalAccessException. You can only use the get method to get the values of accessible fields. The security mechanism of Java lets you find out what fields any object has, but it won't let you read the values of those fields unless you have access permission.

The default behavior of the reflection mechanism is to respect Java access control. However, if a Java program is not controlled by a security manager that disallows it, you can override access control. To do this, invoke the setAccessible method on a Field, Method, or Constructor object, for example:

f.setAccessible(true); // now OK to call f.get(harry);

The setAccessible method is a method of the AccessibleObject class, the common superclass of the Field, Method, and Constructor classes. This feature is provided for debuggers, persistent storage, and similar mechanisms. We use it for a generic toString method later in this section.

There is another issue with the get method that we need to deal with. The name field is a String, and so it is not a problem to return the value as an Object. But suppose we want to look at the salary field. That is a double, and in Java, number types are not objects. To handle this, you can either use the getdouble method of the Field class, or you can call get, whereby the reflection mechanism automatically wraps the field value into the appropriate wrapper class, in this case, D

posted on 2006-07-30 18:49 knowhow 阅读(414) 评论(0)  编辑  收藏 所属分类: Java基础知识

只有注册用户登录后才能发表评论。


网站导航: