Chapter 5. Using reflection

Table of Contents

5.1. Reflection mechanisms
5.2. Reflection elements
5.2.1. Constructors
5.2.2. Instance methods
5.2.3. Static methods
5.3. Standard reflection
5.4. Dynamic reflection
5.5. Reflection implementations
5.5.1. Finding methods by name
5.5.2. Parameter compatibility
5.5.3. Null and primitive parameters
5.5.4. Protected and private constructors and methods

5.1. Reflection mechanisms

Besides defining functions by subclassing a base class or implementing the interface, FunctionalJ also provides mechanisms that use reflection to define a function that refers to an existing constructor or a method.

There are two interfaces that define obtaining functions using reflection: the standard reflection interface and the dynamic reflection interface, both defined in the reflect package:

  • StdReflect
  • DynReflect
The difference between the two is that the standard reflection mechanism requires specifying the types of the parameters of the reflected constructor or method, while the dynamic reflection mechanism does not. The tradeoff is that specifying the types of the parameters targets exactly one constructor or method, improving performance. The resulting code also more specifically indicates which constructor or method is being targetted. On the other hand, not having to specify the parameters is more concise and more flexible.

Another difference is that since standard reflection targets a specific constructor or method according to the types of parameters, it is possible to throw an exception immediately if no suitable target is found. This makes standard reflection a fail-fast mechanism. Dynamic reflection can have several possible targets, and which target to use can only be determined when the function is actually called. Therefore, when creating a function using dynamic reflection, an exception is thrown only if there is absolutely no possible target.

5.2. Reflection elements

The reflection mechanisms create functions, using the FunctionN interface, that target the following:

  • Constructors
  • Instance methods
  • Static methods

5.2.1. Constructors

Targetting a constructor by reflection will produce a function that will return an instance of the target class. The parameters to the function will match those of the target constructor.

5.2.2. Instance methods

An instance method needs an object on which to invoke the method. Therefore, the produced function accepts one more parameter than the number of parameters to the method. That extra parameter is the first parameter, and expects the object on which to invoke the method.

If the object on which to invoke the method is known when creating the function using reflection, it can be specified directly instead of specifying the class that contains the instance method. In that case, the number of parameters expected by produced function will match those of the target instance method, since the object on which to invoke the method is already specified.

5.2.3. Static methods

Static methods do not use an object on which to invoke the method. Therefore, the parameters of the produced function will match those of the target static method.

5.3. Standard reflection

The standard reflection mechanism is defined by the StdReflect interface:

public interface StdReflect
{
  public FunctionN constructor(Class p_class, Class... p_types)
  throws FunctionException;

  public FunctionN constructor(String p_class, Class... p_types)
  throws FunctionException;

  public FunctionN instanceFunction(Class p_class, String p_name, Class... p_types)
  throws FunctionException;

  public FunctionN instanceFunction(String p_class, String p_name, Class... p_types)
  throws FunctionException;

  public FunctionN instanceFunction(Object p_object, String p_name, Class... p_types)
  throws FunctionException;

  public FunctionN staticFunction(Class p_class, String p_name, Class... p_types)
  throws FunctionException;

  public FunctionN staticFunction(String p_class, String p_name, Class... p_types)
  throws FunctionException;
}
Each method of the interface uses reflection to produces a function that, when called, will invoke the target element. You specify the class and the types of the parameters. You can also specify the class by name with a String instead of a Class object. For methods, you must also specify the name of the method. For instance methods, you can specify the target object directly instead of the class.

If no matching target element is found, a FunctionException is thrown. As mentioned earlier, this is an unchecked exception, so handling it is optional.

Here are some examples:

StdReflect reflect = new JdkStdReflect(); // more on implementations later

// constructor

FunctionN<FileInputStream> fileIn =
    reflect.constructor(FileInputStream.class, String.class);

FileInputStream is = fileIn.call("build.xml");

// instance method

FunctionN<Integer> length =
    reflect.instanceFunction(String.class, "length", new Class[0]);

int result = length.call("Abcd");
assertEquals(result, 4);

// instance method, with instance object specified

FunctionN<String> timestamp =
    reflect.instanceFunction(new Date(), "toString", new Class[0]);

String now = timestamp.call();
// now is, for example, "Sun Aug 20 16:09:08 EDT 2006"

// static method

FunctionN<String> valueOf =
    reflect.staticFunction(String.class, "valueOf", Object.class);

String string = valueOf.call(new Integer(7));
assertEquals(string, "7");

5.4. Dynamic reflection

The dynamic reflection mechanism does not require you to specify the types of the parameters. All you need to specify is the class, and, for methods, the name of the method. The mechanism keeps track of the possible targets, and dynamically determines which target to invoke as you specify the parameters.

Here is the DynReflect interface:

public interface DynReflect
{
  public FunctionN constructor(Class p_class)
  throws FunctionException;

  public FunctionN constructor(String p_class)
  throws FunctionException;

  public FunctionN instanceFunction(Class p_class, String p_name)
  throws FunctionException;

  public FunctionN instanceFunction(String p_class, String p_name)
  throws FunctionException;

  public FunctionN instanceFunction(Object p_object, String p_name)
  throws FunctionException;

  public FunctionN staticFunction(Class p_class, String p_name)
  throws FunctionException;

  public FunctionN staticFunction(String p_class, String p_name)
  throws FunctionException;
}
The method signatures are the same as in the StdReflect interface, except that each method does not have the Class[] parameter for specifying the types of the parameters.

5.5. Reflection implementations

By specifying the reflection mechanisms using interfaces, different implementations can be used. FunctionalJ comes with two implementations: one that uses the standard JDK, and one that uses cglib. For functions that are produced using reflection and that are called a large number of times, the cglib implementation may yield better performance.

The implementations are in the reflect.impl package, and implement both the standard and dynamic reflection mechanisms:

  • JdkStdReflect
  • JdkDynReflect
  • CgLibStdReflect
  • CgLibDynReflect

As mentioned in Section 2.3, “Setting up”, you must have the cglib JAR file in your CLASSPATH to use the cglib reflection implementation.

5.5.1. Finding methods by name

The reflection implementations provided in FunctionalJ use a module that finds methods by name. When you specify "name" as the name of a method, the module searches methods looking for the following names, in order of priority:

  • Exact match: "name"
  • Corresponding getter: "getName"
  • Corresponding setter: "setName"
  • Corresponding boolean flag: "isName"

When using standard reflection, the first method that matches the parameter types will be used. For dynamic reflection, a list of possible methods is maintained, and reduced as parameters are specified in order to retain only those methods for which the parameter types are compatible.

The name-finding scheme means that you could, for example, define a function with dynamic reflection using the name of the property of a JavaBean. That same function could be used to invoke both the getter and the setter. When calling the function with no parameters, the getter would be invoked, while calling the function with one parameter would invoke the setter.

5.5.2. Parameter compatibility

With dynamic reflection, parameter compatibility is determined as parameters are supplied to the function, either by binding parameters or by calling the function. When binding parameters, at least one possible target must accept parameters that are compatible with the supplied parameters (with possibly additional parameters). When calling the function, the target that most closely matches the supplied parameters is invoked.

A parameter is considered compatible with the parameter type of a target by considering the following possibilities:

  • the classes of the parameters are an exact match
  • the class of the supplied parameter is a subclass of the parameter type
  • if the parameter type is a primitive type, the type of the supplied parameter is the corresponding wrapper class
  • the classes of the parameters are both arrays and are of compatible types.

5.5.3. Null and primitive parameters

Two types of parameters can cause ambiguity when using dynamic reflection: null, and parameters of a primitive type - int, float, etc.

For null parameters, the reflection mechanism cannot determine the class that is associated with the parameter. Therefore, if a method is overloaded with the same number of parameters, the mechanism cannot determine which method to call.

For primitive parameters, an ambiguity is caused only if a method is overloaded with the same number of parameters, with one method using a parameter of the primitive type, and the other using a parameter that uses the corresponding wrapper class for that primitive type:

public Object something(int p_value);
public Object something(Integer p_value);
The problem is that, when using reflection, parameters of primitive types must be wrapped in their corresponding wrapper class, since the list of parameters when using reflection is of the type Object[]. When the reflection mechanism obtains the parameter, it will be of the type of the wrapper class. There is no way to determine if the original parameter was indeed of the wrapper class, or of the corresponding primitive type.

To resolve these ambiguities, you can use the Parameter class from the reflect package. This class is a wrapper that contains the value of the parameter along with the intended class of the value.

More specifically, you can use NullParameter and PrimitiveParameter, both subclasses of Parameter, to wrap null and primitive parameters respectively.

For example, to invoke the something(int) method in the above situation, you could use:

FunctionN f = reflect.instanceMethod(object, "something");
f.call(new PrimitiveParameter(6));
To call an overloaded method with a null parameter, use new NullParameter(TheClass.class), where TheClass is the class of the parameter accepted by the method that you wish to target.

5.5.4. Protected and private constructors and methods

The default behavior of the JDK implementation of the reflection mechanisms in FunctionalJ allows calling protected and private constructors and methods (as well as those with no access modifier).

On the other hand, note that the cglib implementation does not allow calling non-public constructors or methods by reflection.