Table of Contents
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:
StdReflectDynReflectAnother 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.
The reflection mechanisms create functions, using the
FunctionN interface, that target the following:
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.
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.
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");
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.
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:
JdkStdReflectJdkDynReflectCgLibStdReflectCgLibDynReflect
As mentioned in Section 2.3, “Setting up”, you must have the cglib JAR file in
your CLASSPATH to use the cglib reflection
implementation.
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:
name"getName"setName"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.
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:
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.
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.