Thursday, August 9, 2007

Look inside the Java Reflection class

Java Reflection is a technology that looks inside a Java object at runtime and sees what variables it contains, what methods it supports, what interfaces it implements, what classes it extends—basically everything about the object that you would know at compile time.

The Reflection API is located in the java.lang.reflect package and is included in any J2SE installation. Primarily it is intended for very generic programs such as database browsers or visual code editors, but it can be used in any other applications. Reflection is for dealing with class files you know very little about ahead of time. Reflection has a very high overhead, so before using it, you should make sure that you can't solve your problem with a simple interface, Class.forName(), and a delegate object instead.

Example

Listing A contains an example of code that uses Reflection. The code in Listing A is equivalent to the following code:

newTestClass().setName("TestName");

The code in the first example dynamically calls a method of a newly created object using reflection. It is quite convenient, but it is also relatively slow.

Basic techniques

There are two basic techniques involved in Reflection: discovery and use by name. Here are descriptions of both:

  • Discovery involves taking an object or class and discovering the members, superclasses, implemented interfaces, and then possibly using the discovered elements.
  • Use by name involves starting with the symbolic name of an element and using the named element.

Discovery typically starts with an object and then calls the Object.getClass() method to get the object's Class. The Class object has a number of methods for discovering the contents of the class. Here are some of those methods:

  • getMethods(): returns an array of Method objects representing all of the public methods of the class or interface.
  • getConstructors(): returns an array of Constructor objects representing all of the public constructors of the class.
  • getFields(): returns an array of Field objects representing all of the public fields of the class or interface.
  • getClasses(): returns an array of Class objects representing all of the public classes and interfaces that are members (e.g., inner classes) of the class or interface.
  • getSuperclass(): returns the Class object representing the superclass of the class or interface (null is returned for interfaces).
  • getInterfaces(): returns an array of Class objects representing all of the interfaces that are implemented by the class or interface.

You can obtain the Class object either through discovery, by using the class literal (e.g., MyClass.class), or by using the name of the class (e.g., Class.forName("mypackage.MyClass")). With a Class object, member objects Method, Constructor, or Field can be obtained using the symbolic name of the member. These are the most important techniques:

  • getMethod("methodName", Class...): returns the Method object representing the public method with the name "methodName" of the class or interface that accepts the parameters specified by the Class... parameters.
  • getConstructor(Class...): returns the Constructor object representing the public constructor of the class that accepts the parameters specified by the Class... parameters.
  • getField("fieldName"): returns the Field object representing the public field with the name "fieldName" of the class or interface.

You can use Method, Constructor, and Field objects to dynamically access the represented member of the class. For example:

  • Field.get(Object): returns an Object containing the value of the field from the instance of the object passed to get(). (If the Field object represents a static field, the Object parameter is ignored and may be null.)
  • Method.invoke(Object, Object...): returns an Object containing the result of invoking the method for the instance of the first Object parameter passed to invoke(). The remaining Object... parameters are passed to the method. (If the Method object represents a static method, the first Object parameter is ignored and may be null.)
  • Constructor.newInstance(Object...): returns the new Object instance from invoking the constructor. The Object... parameters are passed to the constructor. (Note that the parameterless constructor for a class can also be invoked by calling newInstance().)

Creating arrays and proxy classes

The java.lang.reflect package provides an Array class that contains static methods for creating and manipulating array objects. Since J2SE 1.3, the java.lang.reflect package also provides a Proxy class that supports dynamic creation of proxy classes, which implement specified interfaces.

The implementation of a Proxy class is provided by a supplied object that implements the InvocationHandler interface. The InvocationHandler's method invoke (Object, Method, Object[]) is called for each method invoked on the proxy object—the first parameter is the proxy object, the second parameter is the Method object representing the method from the interface implemented by the proxy, and the third parameter is the array of parameters passed to the interface method. The invoke() method returns an Object result that contains the result returned to the code that called the proxy interface method.

Further reading

No comments: