Java programming dynamics, Part 2: Introducing reflectionUse run-time class information to limber up your programming ![]() | ![]() |
![]() |
Level: Intermediate Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 03 Jun 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. The starting point for using reflection is always a
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:
If the class has already been loaded, you'll get back the existing
The For each of these three types of class components -- constructors, fields, and methods -- the
Each of these calls returns one or more Listing 1. Class constructed from pair of strings
The code shown in Listing 2 gets the constructor and uses it to create an instance of the Listing 2. Reflection call to constructor
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
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. The
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 Listing 3 shows an example of using the field reflection methods, in the form of a method to increment an Listing 3. Incrementing a field by reflection
This method starts to show some of the flexibility possible with reflection. Rather than working with a specific class, The
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 Listing 4 takes the field example a step further, showing an example of method reflection in action. This method increments an Listing 4. Incrementing a JavaBean property by reflection
To follow the JavaBeans convention, I convert the first letter of the property name to uppercase, then prepend 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 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 The special handling of arrays uses a collection of static methods provided by the 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
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:
There's a simple way around these restrictions, though -- at least sometimes. The Listing 6 demonstrates a program that uses reflection on an instance of the Listing 1 Listing 6. Reflection security in action
If you compile this code and run it directly from the command line without any special parameters, it'll throw an
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 -- Listing 7. Field access performance test code
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
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 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 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. |