|
|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| Use run-time class information to limber up your
programming
Dennis
M. Sosnoski (mailto:dms@sosnoski.com?cc=&subject=Introducing
reflection) President, Sosnoski Software Solutions, Inc. 3 June
2003
Reflection gives your code access to
internal information for classes loaded into the JVM and allows you to
write code that works with classes selected during execution, not in the
source code. This makes reflection a great tool for building flexible
applications. But watch out -- if used inappropriately, reflection can
be costly. In Part 2 of his series on Java platform internals, software
consultant Dennis Sosnoski provides an introduction to using reflection,
as well as a look at some of the costs involved. You'll also find out
how the Java Reflection API lets you hook into objects at run time.
In "Java
programming dynamics, Part 1," I gave you an introduction to Java
programming classes and class loading. That article described some of the
extensive information present in the Java binary class format. This month
I cover the basics of using the Java Reflection API to access and use some
of that same information at run time. To help keep things interesting even
for developers who already know the basics of reflection, I'm including a
look at how reflection performance compares with direct access.
Using reflection is different from normal Java programming in that it
works with metadata -- data
that describes other data. The particular type of metadata accessed by
Java language reflection is the description of classes and objects within
the JVM. Reflection gives you run-time access to a variety of class
information. It even lets you read and write fields and call methods of a
class selected at run time.
Reflection is a powerful tool. It lets you build flexible code that can
be assembled at run time without requiring source code links between
components. But some aspects of reflection can be problematic. In this
article, I'll go into the reasons why you might not want to use
reflection in your programs, as well as the reasons why you would. After
you know the trade-offs, you can decide for yourself when the benefits
outweigh the drawbacks.
Beginners' class The
starting point for using reflection is always a
java.lang.Class instance. If you want to work with a
predetermined class, the Java language provides an easy shortcut to get
the Class instance directly:
Class clas = MyClass.class;
|
When you use this technique, all the work involved in loading the class
takes place behind the scenes. If you need to read the class name at run
time from some external source, however, this approach isn't going to
work. Instead, you need to use a class loader to find the class
information. Here's one way to do that:
// "name" is the class name to load
Class clas = null;
try {
clas = Class.forName(name);
} catch (ClassNotFoundException ex) {
// handle exception case
}
// use the loaded class
|
If the class has already been loaded, you'll get back the existing
Class information. If the class hasn't been loaded yet, the
class loader will load it now and return the newly constructed class
instance.
Reflections on a
class The Class object gives you all the basic
hooks for reflection access to the class metadata. This metadata includes
information about the class itself, such as the package and superclass of
the class, as well as the interfaces implemented by the class. It also
includes details of the constructors, fields, and methods defined by the
class. These last items are the ones most often used in programming, so
I'll give some examples of working with them later in this section.
For each of these three types of class components -- constructors,
fields, and methods -- the java.lang.Class provides four
separate reflection calls to access information in different ways. The
calls all follow a standard form. Here's the set used to find
constructors:
Constructor getConstructor(Class[] params) -- Gets the
public constructor using the specified parameter types
Constructor[] getConstructors() -- Gets all the public
constructors for the class
Constructor getDeclaredConstructor(Class[] params) --
Gets the constructor (regardless of access level) using the specified
parameter types
Constructor[] getDeclaredConstructors() -- Gets all the
constructors (regardless of access level) for the class
Each of these calls returns one or more
java.lang.reflect.Constructor instances. This
Constructor class defines a newInstance method
that takes an array of objects as its only argument, then returns a newly
constructed instance of the original class. The array of objects are the
parameter values used for the constructor call. As an example of how this
works, suppose you have a TwoString class with a constructor
that takes a pair of String s, as shown in Listing 1: Listing 1. Class constructed from pair of
strings
public class TwoString {
private String m_s1, m_s2;
public TwoString(String s1, String s2) {
m_s1 = s1;
m_s2 = s2;
}
}
|
The code shown in Listing 2 gets the constructor and uses it to create
an instance of the TwoString class using String s
"a" and "b" : Listing 2.
Reflection call to constructor
Class[] types = new Class[] { String.class, String.class };
Constructor cons = TwoString.class.getConstructor(types);
Object[] args = new Object[] { "a", "b" };
TwoString ts = cons.newInstance(args);
|
The code in Listing 2 ignores several possible types of checked
exceptions thrown by the various reflection methods. The exceptions are
detailed in the Javadoc API descriptions, so in the interest of
conciseness, I'm leaving them out of all the code examples.
While I'm on the topic of constructors, the Java programming language
also defines a special shortcut method you can use to create an instance
of a class with a no-argument (or
default) constructor. The shortcut is embedded into the Class
definition itself like this:
Object
newInstance() -- Constructs new instance using default
constructor
Even though this approach only lets you use one particular constructor,
it makes a very convenient shortcut if that's the one you want. This
technique is especially useful when working with JavaBeans, which are
required to define a public, no-argument constructor.
Fields by
reflection The Class reflection calls to access
field information are similar to those used to access constructors, with a
field name used in place of an array of parameter types:
Field getField(String name) -- Gets the named public
field
Field[] getFields() -- Gets all public fields of the
class
Field getDeclaredField(String name) -- Gets the named
field declared by the class
Field[] getDeclaredFields() -- Gets all the fields
declared by the class
Despite the similarity to the constructor calls, there's one important
difference when it comes to fields: the first two variants return
information for public fields that can be accessed through the class --
even those inherited from an ancestor class. The last two return
information for fields declared directly by the class -- regardless of the
fields' access types.
The java.lang.reflect.Field instances returned by the
calls define getXXX and setXXX methods for all
the primitive types, as well as generic get and
set methods that work with object references. It's up to you
to use an appropriate method based on the actual field type, though the
getXXX methods will handle widening conversions automatically
(such as using the getInt method to retrieve a
byte value).
Listing 3 shows an example of using the field reflection methods, in
the form of a method to increment an int field of an object
by name: Listing 3. Incrementing a field by
reflection
public int incrementField(String name, Object obj) throws... {
Field field = obj.getClass().getDeclaredField(name);
int value = field.getInt(obj) + 1;
field.setInt(obj, value);
return value;
}
|
This method starts to show some of the flexibility possible with
reflection. Rather than working with a specific class,
incrementField uses the getClass method of the
passed-in object to find the class information, then finds the named field
directly in that class.
Methods by
reflection The Class reflection calls to access
method information are very similar to those used for constructors and
fields:
Method getMethod(String name, Class[] params) -- Gets
the named public method using the specified parameter types
Method[] getMethods() -- Gets all public methods of the
class
Method getDeclaredMethod(String name, Class[] params)
-- Gets the named method declared by the class using the specified
parameter types
Method[] getDeclaredMethods() -- Gets all the methods
declared by the class
As with the field calls, the first two variants return information for
public methods that can be accessed through the class -- even those
inherited from an ancestor class. The last two return information for
methods declared directly by the class, without regard to the access type
of the method.
The java.lang.reflect.Method instances returned by the
calls define an invoke method you can use to call the method
on an instance of the defining class. This invoke method
takes two arguments, which supply the class instance and an array of
parameter values for the call.
Listing 4 takes the field example a step further, showing an example of
method reflection in action. This method increments an int
JavaBean property defined with get and set
methods. For example, if the object defined getCount and
setCount methods for an integer count value, you
could pass "count" as the name parameter in a call to this
method in order to increment that value. Listing 4.
Incrementing a JavaBean property by reflection
public int incrementProperty(String name, Object obj) {
String prop = Character.toUpperCase(name.charAt(0)) +
name.substring(1);
String mname = "get" + prop;
Class[] types = new Class[] {};
Method method = obj.getClass().getMethod(mname, types);
Object result = method.invoke(obj, new Object[0]);
int value = ((Integer)result).intValue() + 1;
mname = "set" + prop;
types = new Class[] { int.class };
method = obj.getClass().getMethod(mname, types);
method.invoke(obj, new Object[] { new Integer(value) });
return value;
}
|
To follow the JavaBeans convention, I convert the first letter of the
property name to uppercase, then prepend get to construct the
read method name and set to construct the write method name.
JavaBeans read methods just return the value and write methods take the
value as the only parameter, so I specify the parameter types for the
methods to match. Finally, the convention requires the methods to be
public, so I use the form of lookup that finds public methods callable on
the class.
This example is the first one where I've passed primitive values using
reflection, so let's look at how this works. The basic principle is
simple: whenever you need to pass a primitive value, just substitute an
instance of the corresponding wrapper class (defined in the
java.lang package) for that type of primitive. This applies
to both calls and returns. So when I call the get method in
my example, I expect the result to be a java.lang.Integer
wrapper for the actual int property value.
Reflecting
arrays Arrays are objects in the Java programming language.
Like all objects, they have classes. If you have an array, you can get the
class of that array using the standard getClass method, just
as with any other object. However, getting the class without an
existing instance works differently than for other types of objects. Even
after you have an array class there's not much you can do with it directly
-- the constructor access provided by reflection for normal classes
doesn't work for arrays, and arrays don't have any accessible fields. Only
the base java.lang.Object methods are defined for array
objects.
The special handling of arrays uses a collection of static methods
provided by the java.lang.reflect.Array class. The methods in
this class let you create new arrays, get the length of an array object,
and read and write indexed values of an array object.
Listing 5 shows a useful method for effectively resizing an existing
array. It uses reflection to create a new array of the same type, then
copies all the data across from the old array before returning the new
array. Listing 5. Growing an array by
reflection
public Object growArray(Object array, int size) {
Class type = array.getClass().getComponentType();
Object grown = Array.newInstance(type, size);
System.arraycopy(array, 0, grown, 0,
Math.min(Array.getLength(array), size));
return grown;
}
|
Security and
reflection Security can be a complex issue when dealing with
reflection. Reflection is often used by framework-type code, and for this
you may want the framework to have full access to your code without
concern for normal access restrictions. Yet uncontrolled access can create
major security risks in other cases, such as when code is executed in an
environment shared by untrusted code.
Because of these conflicting needs, the Java programming language
defines a multi-level approach to handling reflection security. The basic
mode is to enforce the same restrictions on reflection as would apply for
source code access:
- Access from anywhere to public components of the class
- No access outside the class itself to private components
- Limited access to protected and package (default access)
components
There's a simple way around these restrictions, though -- at least
sometimes. The Constructor , Field , and
Method classes I've used in the earlier examples all extend a
common base class -- the java.lang.reflect.AccessibleObject
class. This class defines a setAccessible method that lets
you turn the access checks on or off for an instance of one of these
classes. The only catch is that if a security manager is present, it will
check that the code turning off access checks has permission to do so. If
there's no permission, the security manager throws an exception.
Listing 6 demonstrates a program that uses reflection on an instance of
the Listing 1TwoString class to show this in
action: Listing 6. Reflection security in
action
public class ReflectSecurity {
public static void main(String[] args) {
try {
TwoString ts = new TwoString("a", "b");
Field field = clas.getDeclaredField("m_s1");
// field.setAccessible(true);
System.out.println("Retrieved value is " +
field.get(inst));
} catch (Exception ex) {
ex.printStackTrace(System.out);
}
}
}
|
If you compile this code and run it directly from the command line
without any special parameters, it'll throw an
IllegalAccessException on the field.get(inst)
call. If you uncomment the field.setAccessible(true) line,
then recompile and rerun the code, it will succeed. Finally, if you add
the JVM parameter -Djava.security.manager on the command line
to enable a security manager, it will again fail, unless you define
permissions for the ReflectSecurity class.
Reflection
performance Reflection is a powerful tool, but suffers from
a few drawbacks. One of the main drawbacks is the effect on performance.
Using reflection is basically an interpreted operation, where you tell the
JVM what you want to do and it does it for you. This type of operation is
always going to be slower than just doing the same operation directly. To
demonstrate the performance cost of using reflection, I prepared a set of
benchmark programs for this article (see Resources for a link to the full code).
Listing 7 shows an excerpt from the field access performance test,
including the basic test methods. Each method tests one form of access to
fields -- accessSame works with member fields of the same
object, accessOther uses fields of another object accessed
directly, and accessReflection uses fields of another object
accessed by reflection. In each case, the methods perform the same
computations -- a simple add/multiply sequence in a loop. Listing 7. Field access performance test code
public int accessSame(int loops) {
m_value = 0;
for (int index = 0; index < loops; index++) {
m_value = (m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return m_value;
}
public int accessReference(int loops) {
TimingClass timing = new TimingClass();
for (int index = 0; index < loops; index++) {
timing.m_value = (timing.m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return timing.m_value;
}
public int accessReflection(int loops) throws Exception {
TimingClass timing = new TimingClass();
try {
Field field = TimingClass.class.
getDeclaredField("m_value");
for (int index = 0; index < loops; index++) {
int value = (field.getInt(timing) +
ADDITIVE_VALUE) * MULTIPLIER_VALUE;
field.setInt(timing, value);
}
return timing.m_value;
} catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}
|
The test program calls each method repeatedly with a large loop count,
averaging the time measurements over several calls. The time for the first
call to each method is not included in the average, so initialization time
isn't a factor in the results. In the test runs for this article, I used a
loop count of 10 million for each call, running on a 1GHz PIIIm system. My
timing results with three different Linux JVMs are shown in Figure 1. All
tests used the default settings for each JVM.
Figure 1. Field access times
The logarithmic scale of the chart allows the full range of times to be
displayed, but lessens the visual impact of the differences. In the case
of the first two sets of figures (the Sun JVMs), the execution time using
reflection is over 1000 times greater than that using direct access. The
IBM JVM does somewhat better by comparison, but the reflection method
still takes more than 700 times as long as the other methods. There were
no significant differences in times between the other two methods on any
JVM, though the IBM JVM did run these almost twice as fast as the Sun
JVMs. Most likely, this difference reflects the specialized optimizations
used by the Sun Hot Spot JVMs, which tend to do poorly in simple
benchmarks.
Besides the field access time tests, I did the same sort of timing test
for method calls. For method calls, I tried the same three access
variations as for field access, with the added variable of using
no-argument methods versus passing and returning a value on the method
calls. Listing 8 shows the code for the three methods used to test the
passed-and-returned value form of the calls. Listing 8. Method access performance test
code
public int callDirectArgs(int loops) {
int value = 0;
for (int index = 0; index < loops; index++) {
value = step(value);
}
return value;
}
public int callReferenceArgs(int loops) {
TimingClass timing = new TimingClass();
int value = 0;
for (int index = 0; index < loops; index++) {
value = timing.step(value);
}
return value;
}
public int callReflectArgs(int loops) throws Exception {
TimingClass timing = new TimingClass();
try {
Method method = TimingClass.class.getMethod
("step", new Class [] { int.class });
Object[] args = new Object[1];
Object value = new Integer(0);
for (int index = 0; index < loops; index++) {
args[0] = value;
value = method.invoke(timing, args);
}
return ((Integer)value).intValue();
} catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}
|
Figure 2 shows my timing results for method calls. Here again,
reflection is much slower than the direct alternative. The differences
aren't quite as large as for the field access case, though, ranging from
several hundred times slower on the Sun 1.3.1 JVM to less than 30 times
slower on the IBM JVM for the no-argument case. The test performance for
reflection method calls with arguments are substantially slower than the
calls with no arguments on all JVMs. This is probably partially because of
the java.lang.Integer wrapper needed for the int
value passed and returned. Because Integer s are immutable, a
new one needs to be generated for each method return, adding considerable
overhead.
Figure 2. Method call times
Reflection performance was one area of focus for Sun when developing
the 1.4 JVM, which shows in the reflection method call results. The Sun
1.4.1 JVM shows greatly improved performance over the 1.3.1 version for
this type of operation, running about seven times faster in my tests. The
IBM 1.4.0 JVM again delivered even better performance for this simple
test, though, running two to three times faster than the Sun 1.4.1
JVM.
I also wrote a similar timing test program for creating objects using
reflection. The differences for this case aren't nearly as significant as
for the field and method call cases, though. Constructing a simple
java.lang.Object instance with a newInstance()
call takes about 12 times longer than using new Object() on
the Sun 1.3.1 JVM, about four times longer on the IBM 1.4.0 JVM, and only
about two times longer on the Sun 1.4.1 JVM. Constructing an array using
Array.newInstance(type, size) takes a maximum of about two
times longer than using new type[size] for any tested JVM,
with the difference decreasing as the array size grows.
Reflection
summary Java language reflection provides a very versatile
way of dynamically linking program components. It allows your program to
create and manipulate objects of any classes (subject to security
restrictions) without the need to hardcode the target classes ahead of
time. These features make reflection especially useful for creating
libraries that work with objects in very general ways. For example,
reflection is often used in frameworks that persist objects to databases,
XML, or other external formats.
Reflection also has a couple of drawbacks. One is the performance
issue. Reflection is much slower than direct code when used for field and
method access. To what extent that matters depends on how reflection is
used in a program. If it's used as a relatively infrequent part of the
program's operation, the slow performance won't be a concern. Even the
worst-case timing figures in my tests showed reflection operations taking
only a few microseconds. The performance issues only become a serious
concern if reflection is used in the core logic of performance-critical
applications.
A more serious drawback for many applications is that using reflection
can obscure what's actually going on inside your code. Programmers expect
to see the logic of a program in the source code, and techniques such as
reflection that bypass the source code can create maintenance problems.
Reflection code is also more complex than the corresponding direct code,
as can be seen in the code samples from the performance comparisons. The
best ways to deal with these issues are to use reflection sparingly --
only in the places where it really adds useful flexibility -- and document
its use within the target classes.
In the next installment, I'll give a more detailed example of how to
use reflection. This example provides an API for processing command line
arguments to a Java application, a tool you may find useful for your own
applications. It also builds on the strengths of reflection while avoiding
the weaknesses. Can reflection simplify your command line processing? Find
out in Part 3 of Java programming
dynamics.
Resources
- Download the performance benchmarks programs used in this
article.
- Get a historical perspective on the growing capabilities of Java
reflection in "Reflection: A new way to discover information about Java
classes" (developerWorks,
May 1998).
- Reflection plays an especially important role in working with
JavaBeans components. Learn all about this in "Reflecting, introspecting, and customizing
JavaBeans" (developerWorks,
February 2000).
- Extensive use of reflection can have harmful effects on framework
performance. For a graphic example and some related discussions, see the
author's XML data binding articles "Data binding, Part 2: Performance" (developerWorks,
January 2003) and "Data binding, Part 3: JiBX architecture" (developerWorks,
April 2003).
- For an in-depth tutorial on using reflection, try The Reflection API trail in the Java Tutorial from
Sun.
- Find hundreds more Java technology resources on the developerWorks Java technology
zone.
About the
author Dennis
Sosnoski is the founder and lead consultant of Seattle-area Java
consulting company Sosnoski Software Solutions, Inc., specialists
in J2EE, XML, and Web services support. His professional software
development experience spans over 30 years, with the last several
years focused on server-side Java technologies. Dennis is a frequent
speaker on XML and Java technologies at conferences nationwide, and
chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com. |
|
|