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 class or object 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, as well as 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. And you can do all of this on objects that
your code has never even seen before.
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 JavaBeans to determine the capabilities of objects at runtime. 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 (ordinary, compiled) 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 separate classes in
the reflection API: java.lang.reflect.Field
,
java.lang.reflect.Method
, and
java.lang.reflect.Constructor
. We can create these
objects using the Class
object.
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. Some examples:
getFields( )
returns an array ofField
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( )
andgetDeclared-Constructors( )
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—by signature (for example, getField( )
, which takes the field name as an argument).
The following listing shows the
methods in
the Class
class:
-
Field []
getFields
( );
-
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
( );
-
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 all 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 a particular instance of
Calendar
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( );
Access to the reflection API is governed by a security manager. A fully trusted application has access to all of the previously discussed 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; currently, user code can see only 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 class loader), but can rely only on the ability to access the public members of public classes that originate elsewhere.
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,
consider this 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"); // read it int mybalance = balanceField.getInt( myBankAccount ); // change it balanceField.setInt( myBankAccount, 42 ); } catch ( NoSuchFieldException e ) { ... // there is no "balance" field in this class } catch ( IllegalAccessException e2) { ... // we don't have permission to access the field }
In this example, we are assuming that we already know the structure
of a BankAccount
object. However the real power of
reflection is in examining objects that we’ve never seen
before.
The various methods of Field
take a reference to
the particular object instance that we want to access. In the code
shown earlier, 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
particular account we want to work with. An exception occurs if we
try to access to a field that doesn’t exist, or if we
don’t have the proper permission to read or write to 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 runtime, and could use this technique
to examine the balance
field in a class that was
dynamically loaded.
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:
//file: Invoke.java 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 occurred 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
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 an int
.
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 the
wrapper class that represents 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 method. 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, you must use wrapper classes
for primitive argument types.
The exceptions shown in the previous code occur if we can’t
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
.
The
java.lang.reflect.Constructor
class represents an object
constructor that accepts arguments. You can use it, subject to the
security manager, to create a new instance of an object. (Recall that
you can create instances of a class with Class.newInstance( )
, but you cannot specify arguments with
that method.)
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, 2000" } ); 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 class 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 exceptions from the previous example apply here, too, along with
IllegalArgumentException
and
InstantiationException
.
The latter is thrown if the class is abstract
, and
so can’t be instantiated.
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 Java language
reference.
Ideally, Java
reflection would
allow us to do everything at
runtime that we can do at compile time (without forcing us to
generate and compile source into byte-code). But prior to SDK 1.3,
there was an important piece missing from the puzzle. Although we
could dynamically load and create instances of objects at runtime
using the Class.forName( )
, there was no way to
create new types or implementations of objects—for which no
class files pre-exist—on the fly.
In SDK 1.3, the
java.lang.reflect.Proxy
class takes a step towards solving this
problem, by allowing the creation of adapter objects that implement
arbitrary interfaces. The Proxy
class is a factory
that can generate an adapter class implementing any interface you
want. When methods are invoked on the adapter class, they are
delegated to a designated InvocationHandler
object. You can use this to create implementations of any kind of
interface at runtime and handle the method calls anywhere you want.
This is particularly important for tools that work with JavaBeans,
which must dynamically register event listeners. (We’ll mention
this again in Chapter 19.)
In the following snippet, we take an interface name and construct a proxy implementing the interface. It will output a message whenever any of the interface’s methods is invoked.
import java.lang.reflect.*; InvocationHandler handler = new InvocationHandler( ) { public Object invoke( Object proxy, Method method, Object[] args ) { System.out.println( "Method: "+ method.getName() +"( )" +" of interface: "+ interfaceName + " invoked on proxy." ); return null; } }; Class clas = Class.forName( interfaceName ); Object interfaceProxy = Proxy.newProxyInstance( clas.getClassLoader( ), new Class[] { clas }, handler );
The resulting object, interfaceProxy
, can be cast
to the type of the interface we specified in
interfaceName
. It will call our handler whenever
any of its methods is called.
First we make an implementation of
InvocationHandler
. This is an object with an
invoke( )
method
that takes as its argument the Method
being called
and an array of objects representing the arguments to the method
call. Then we fetch the class of the interface that we’re going
to implement using Class.forName( )
. Finally we ask the proxy to create an
adapter for us, specifying the types of interfaces (you can specify
more than one) that we want implemented and the handler to use.
invoke( )
is expected to return an object of the
correct type for the method call. If it returns the wrong type, a
special runtime exception is thrown. Any primitive types in the
arguments or in the return value should be wrapped in the appropriate
wrapper class. (The runtime system unwraps the return value, if
necessary.)
In Chapter 19, we’ll learn how reflection is used 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 19, we’ll also develop a dynamic adapter class using reflection. But as a general coding practice, dynamic method invocation is a 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 a source code interpreter in Java that could access the full Java APIs, 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. In fact someone has done this—one of the authors of this book!
Pat here . . . I can’t resist inserting a plug here for BeanShell—my free, open source, light-weight Java scripting language. BeanShell is just what I alluded to in the previous section—a Java application that uses the reflection API to execute Java statements and expressions dynamically. You can use BeanShell interactively to quickly try out some of the examples in this book (although you can’t create classes per se). BeanShell exercises the Java reflection API to its fullest and serves as a demonstration of how dynamic the Java runtime environment really is.
You can find a copy of BeanShell on the CD-ROM that accompanies this book, on the book’s web page, http://www.oreilly.com/catalog/learnjava, or at http://www.beanshell.org. See Appendix B, for more information on getting started. I hope you find it both interesting and useful!
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.