Полезная информация

Exploring Java

Previous: 7.2 The Class ClassChapter 7
Working with Objects and Classes
Next: 8. Threads
 

7.3 Reflection

In this section we'll take a look at the Java reflection API, supported by the classes in the java.lang.reflect package. As its name suggests, reflection is the ability for a programming language to examine itself. Reflection lets Java code look at an object (more precisely, the class of the object) and determine its structure. Within the limits imposed by the security manager, you can find out what constructors, methods, and fields a class has, and their attributes. You can even change the value of fields, dynamically invoke methods, and construct new objects, much as if Java had primitive pointers to variables and methods.

We don't have room here to fully cover the reflection API. As you might expect, the reflect package is complex and rich in details. But reflection has been designed so that you can do a lot with relatively little effort; 20 percent of the effort will give you 80 percent of the fun.

The reflection API is used by Java Beans to determine the capabilities of objects at run-time. It's also used at a lower level by object serialization to tear apart and build objects for transport over streams or into persistent storage. Obviously, the power to pick apart objects and see their internals must be zealously guarded by the security manager. Your code is not allowed to do anything with the reflection API that it couldn't do with static Java code. In short, reflection is a powerful tool, but it isn't a loophole. An object can't use it to find out about data fields that it wouldn't normally be able to access (for example, another object's private fields), and you can't use it to modify any data inappropriately.

The three primary features of a class are its fields (variables), methods, and constructors. For purposes of describing or accessing an object, these three features are represented by the classes in the reflection API: the java.lang.reflect.Field, java.lang.reflect.Method, and java.lang.reflect.Constructor classes represent the fields, methods, and constructors of a class. To get one of these objects, we use the class's Class.

Table 7.1 shows that the Class class provides two pairs of methods for getting at each type of feature. One pair allows access to a class's public features (including those inherited from its superclasses), while the other pair allows access to any public or nonpublic item declared within the class (but not features that are inherited), subject to security considerations. For example, getFields() returns an array of Field objects representing all of a class's public variables, including those it inherits. getDeclaredFields() returns an array representing all the variables declared in the class, regardless of their access modifiers (not including variables the security manager won't let you see), but not including inherited variables. (For constructors, the distinction between "all constructors" and "declared constructors" is meaningful, so getConstructors() and getDeclaredConstructors() differ only in that the former returns public constructors, while the latter returns all the class's constructors.) Each pair of methods includes a method for listing all of the items at once (for example, getFields()), and a method for looking up a particular item by name and (for methods and constructors) signature (for example, getField(), which takes the field name as an argument).

Table 7.1: Methods for Analyzing a Class
MethodDescription
Field [] getFields();

Get the public variables, including inherited ones.

Field getField(String name);

Get the specified public variable, which may be inherited.

Field [] getDeclaredFields();

Get all public and nonpublic variables declared in this class (not including those inherited from superclasses).

Field getDeclaredField(String name);

Get the specified variable, public or nonpublic, declared in this class (inherited variables not considered).

Method [] getMethods();

Get the public methods, including inherited ones.

Method getMethod(String name,Class [] argumentTypes);

Get the specified public method, whose arguments match the types listed in argumentTypes. The method may be inherited.

Method [] getDeclaredMethods();

Get all public and nonpublic methods declared in this class (not including those inherited from superclasses).

Method getDeclaredMethod(String name,Class [] argumentTypes);

Get the specified method, public or nonpublic, whose arguments match the types listed in argumentTypes, and which is declared in this class (inherited methods not considered).

Constructor [] getConstructors();

Get the public constructors of this class.

Constructor getConstructor(Class [] argumentTypes);

Get the specified public constructor of this class, whose arguments match the types listed in argumentTypes.

Constructor [] getDeclaredConstructors();

Get all public and nonpublic constructors of this class.

Constructor getDeclaredConstructor(Class [] argumentTypes);

Get the specified constructor, public or nonpublic, whose arguments match the types listed in argumentTypes.

As a quick example, we'll show how easy it is to list all of the public methods of the java.util.Calendar class:

Method [] methods = Calendar.class.getMethods();
for (int i=0; i < methods.length; i++)
    System.out.println( methods[i] );

Here we have used the .class notation to get a reference to the Class of Calendar. Remember the discussion of the Class class--the reflection methods don't belong to the Calendar class itself; they belong to the java.lang.Class object that describes the Calendar class. If we wanted to start from an instance of Calendar (or, say, an unknown object), we could have used the getClass() method of the object instead:

Method [] methods = myUnknownObject.getClass().getMethods();

7.3.1 Security

Access to the reflection API is governed by a security manager. A fully trusted application has access to all of the above functionality--it can gain access to members of classes at the level of restriction normally granted code within its scope. There is currently no "special" access granted by the reflection API. It is possible that in the future, the full power of the reflection API will be available to completely trusted code such as debuggers; right now, user code can only see what it could have seen at compile-time. Untrusted code (for example, an unsigned applet) has the normal level of access to classes loaded from its own origin (classes sharing its classloader), but can only rely on the ability to access the public members of public classes coming from the rest of the system.

7.3.2 Accessing Fields

The class java.lang.reflect.Field is used to represent static variables and instance variables. Field has a full set of accessor methods for all of the base types (for example, getInt() and setInt(), getBoolean() and setBoolean()), and get() and set() methods for accessing members that are object references.

For example, given the following class:

class BankAccount {
    public int balance;
}

with the reflection API we can read and modify the value of the public integer field balance:

BankAccount myBankAccount = ...;
...
try {
    Field balanceField = BankAccount.class.getField("balance");
    int balance = balanceField.getInt( myBankAccount );  // read it
    balanceField.setInt( myBankAccount, 42 );            // change it
} catch ( NoSuchFieldException e ) { 
    // There is no "balance" field in this class
} catch ( IllegalAccessException e2) {
    // We don't have permission to access the field.
}

The various methods of Field take a reference to the particular object instance that we want to access. In the code above, the getField() method returns a Field object that represents the balance of the BankAccount class; this object doesn't refer to any specific BankAccount. Therefore, to read or modify any specific BankAccount, we call getInt() and setInt() with a reference to myBankAccount, which is the account we want to work with. As you can see, an exception occurs if we ask for access to a field that doesn't exist, or if we don't have the proper permission to read or write the field. If we make balance a private field, we can still look up the Field object that describes it, but we won't be able to read or write its value.

Therefore, we aren't doing anything that we couldn't have done with static code at compile-time; as long as balance is a public member of a class that we can access, we can write code to read and modify its value. What's important is that we're accessing balance at run-time, and could use this technique to examine the balance field in a class that was dynamically loaded.

7.3.3 Accessing Methods

The class java.lang.reflect.Method represents a static or instance method. Subject to the normal security rules, a Method object's invoke() method can be used to call the underlying object's method with specified arguments. Yes, Java has something like a method pointer!

As an example, we'll write a Java application called invoke that takes as command-line arguments the name of a Java class and the name of a method to invoke. For simplicity, we'll assume that the method is static and takes no arguments:

import java.lang.reflect.*;
class invoke {
    public static void main( String [] args ) {
        try {
            Class c = Class.forName( args[0] );
            Method m = c.getMethod( args[1], new Class [] { } );
            Object ret =  m.invoke( null, null );
            System.out.println( "Invoked static method: " + args[1] + 
                " of class: " + args[0] + " with no args\nResults: " + ret );
        } catch ( ClassNotFoundException e ) {
            // Class.forName() can't find the class
        } catch ( NoSuchMethodException e2 ) {
            // that method doesn't exist
        } catch ( IllegalAccessException e3 ) {
            // we don't have permission to invoke that method
        } catch ( InvocationTargetException e4 ) {
            // an exception ocurred while invoking that method
            System.out.println("Method threw an: " + e4.getTargetException() );
        }
    }
}

We can run invoke to fetch the value of the system clock:

% java invoke java.lang.System currentTimeMillis
Invoked static method: currentTimeMillis of class: java.lang.System with no args 
Results: 861129235818

Turning to the code, our first task is to look up the specified Class by name. To do so, we call the forName() method with the name of the desired class (the first command-line argument). We then ask for the specified method by its name. getMethod() has two arguments: the first is the method name (the second command-line argument), and the second is an array of Class objects that specifies the method's signature. (Remember that any method may be overloaded; you must specify the signature to make it clear which version you want.) Since our simple program calls only methods with no arguments, we create an anonymous empty array of Class objects. Had we wanted to invoke a method that takes arguments, we would have passed an array of the classes of their respective types, in the proper order. For primitive types we would have used the necessary wrappers. The classes of primitive types are represented by the static TYPE fields of their respective wrappers; for example, use Integer.TYPE for the class of a primitive integer.

Once we have the Method object, we call its invoke() method. This calls our target method and returns the result as an Object. (To do anything nontrivial with this object, you have to cast it to something more specific. Presumably, since you're calling the method, you know what kind of object to expect.) If the returned value is a primitive type like int or boolean, it will be wrapped in the standard wrapper class for its type. (Wrappers for primitive types are discussed in Chapter 9.) If the method returns void, invoke() returns a Void object. (This is why a wrapper class is needed for void; we need a class to represent void return values.)

The first argument to invoke() is the object on which we would like to invoke the method. If the method is static, there is no object, so we set the first argument to null. That's the case in our example. The second argument is an array of objects to be passed as arguments to the methods. The types of these should match the types specified in the call to getMethod(). Because we're calling a method with no arguments, we can pass null for the second argument to invoke(). As with the return value, the types of primitive arguments are expected to be wrapped in wrapper classes. The reflection API automatically unpacks them for the method invocation.

The exceptions shown in the code above occur if we cannot find or don't have permission to access the method. Additionally, an InvocationTargetException occurs if the method being invoked throws some kind of exception itself. You can find out what it threw by calling the getTargetException() method of InvocationTargetException.

7.3.4 Accessing Constructors

The java.lang.reflect.Constructor class represents an object constructor. You can use it, subject to the security manager, to create a new instance of an object, with arguments. Although you can load new classes dynamically and create instances of them with Class.forName() and Class.newInstance(), you cannot specify arguments with those methods.

Here we'll create an instance of java.util.Date, passing a string argument to the constructor:

try {
    Constructor c = Date.class.getConstructor( new Class [] { String.class } );
    Object o = c.newInstance( new Object [] { "Jan 1, 1997" } );
    Date d = (Date)o;
    System.out.println(d);
} catch ( NoSuchMethodException e ) {
    // getConstructor() couldn't find the constructor we described
} catch ( InstantiationException e2 ) {
    // the class is abstract 
} catch ( IllegalAccessException e3 ) {
    // we don't have permission to create an instance
} catch ( InvocationTargetException e4 ) {
    // the construct threw an exception
}

The story is much the same as with a method invocation; after all, a constructor is really no more than a method with some strange properties. We look up the appropriate constructor for our Date class--the one that takes a single String as its argument--by passing getConstructor() an array containing the String class as its only element. (If the constructor required more arguments, we would put additional objects in the array, representing the classes of each argument.) We can then invoke newInstance(), passing it a corresponding array of argument objects. Again, to pass primitive types, we would wrap them in their wrapper types first. Finally, we cast the resulting object to a Date and print it.

The same exceptions seen in the previous example apply here, including the possible IllegalArgumentException. In addition, newInstance() throws an InstantiationException if the class is abstract and cannot be instantiated.

What about arrays?

The reflection API allows you to create and inspect arrays of base types using the java.lang.reflect.Array class. The process is very much the same as with the other classes, so we won't cover it here. For more information, look in your favorite language reference.

What is reflection good for?

We've already said that reflection is used by the serialization process. In Chapter 18 we'll learn how it is used by JavaBeans to dynamically discover capabilities and features of Java Bean objects. But these are somewhat behind-the-scenes applications. What can reflection do for us in everyday situations?

Well, we could use reflection to go about acting as if Java had dynamic method invocation and other useful capabilities; in Chapter 18, we'll develop a dynamic adapter class using reflection. But as a general coding practice, dynamic method invocation is a very bad idea. One of the primary features of Java is its strong typing and safety. You abandon much of that when you take a dip in the reflecting pool.

More appropriately, you can use reflection in situations where you need to work with objects that you can't know about in advance. Reflection puts Java on a higher plane of programming languages, opening up possibilities for new kinds of applications. As we hinted earlier, one of the most important uses for reflection will be in integrating Java with scripting languages. With reflection one could write an interpreter in Java that could access the full Java API, create objects, invoke methods, modify variables and do all of the other things that a Java program can do at compile-time, while it is running. The invoke example from a few pages back should give you a brief hint about how to write this interpreter.


Previous: 7.2 The Class ClassExploring JavaNext: 8. Threads
7.2 The Class ClassBook Index8. Threads

Other Books in this LibraryJava in a NutshellJava Language ReferenceJava AWTJava Fundamental ClassesExploring Java