Here's how the Java Reflection APIs can be used to provide ad hoc polymorphism support.
July 01, 2004
URL:http://www.drdobbs.com/jvm/java-reflection-smalltalk-like-method-d/184405725
Barry is a member of the IBM Worldwide Accessibility Center, where he is part of a team that helps IBM make its products accessible to people with disabilities. He also serves as an Adjunct Assistant Professor of Computer Science at the University of Texas, Austin, and is a Sun Certified Java Programmer, Developer, and Architect. You can contact him at feigenbaus.ibm.com.
Java is a strongly typed language, which means all variables must have a type and one can only call (through such variables) methods defined for that specific type. While this ensures that type errors are caught at compile time, it restricts certain types of method calls supported by other popular object-oriented languages such as Smalltalk and Python.
The alternative approach is to have typeless variables (in Java this is the equivalent of declaring all variables to be of type java.lang.Object). With typeless variables, when a method is invoked, the runtime determines if the method is implemented by the receiving object. If it is, the method is executed; otherwise, a runtime error (similar to Java's java.lang.NoSuchMethodException) is generated.
This approach to method invocation, which I call "ad hoc polymorphism," has several advantages:
In this article, I show how you can use the Java Reflection APIs to build a simple library that provides ad hoc polymorphism support for Java programmers. In the process, I present examples that compare and contrast this new type of programming with Java's style.
For example, say you have several classesBird, Plane, Angel, and Leaf. In some sense, they can all fly. To be able to polymorphically create a fly method in Java, they would all need a common superclass or interface that defines a fly method. Because they have little in common, it would be difficult to create a reasonable class inheritance hierarchy to position the fly method in. But with ad hoc polymorphism, this is trivialyou simply define the fly method in each of these classes. Once this is done, you can invoke the fly method on any instance of these classes. Example 1(a) continues this example using these class definitions (in pseudocode), so that in Example 1(b), all of these method calls are valid.
Using a more concrete example, consider the Python code in Listing One (excerpted from Reflect.py, which is available electronically; see "Resource Center," page 3) that defines five classes, Test1 through Test5.
To avoid redundancy, Listing Two uses two Python featureslambda (or anonymous) functions and first-class functions that can be passed as parameters. The testit method calls the supplied function. Listings Three through Eight are the main code. Listings Four through Eight use the testit method to demonstrate how methods are invoked. This results in the output: For value: This is a test string. Next, invoke the find (similar to Java's java.lang.String.indexOf) method to search for a substring in a string (Listing Four), which demonstrates method dispatching similar to Java's. (Output, presented in italics, follows each segment of code.) Listing Five illustrates ad hoc polymorphism-based dispatching. Notice that only one (untyped) variable, t, is used for all references, regardless of the referenced object's type.
For both Test1 and Test2, the existing method1 is called, and the missing method2 generates an error. Although there is no relationship between classes Test1 and Test2, method1 is successfully found and called. For Test3 in Listing Six, method1 generates an error and the existing method2 is called. Similar to Listing Six, except for Test4, both existing methods are successfully called in Listing Seven.
For Test4, method1 is resolved by inheritance. Listing Seven shows that ad hoc polymorphism still respects inheritance.
For Test5, method1 is overridden, while the missing method2 is not (Listing Eight). Whenever a default value is provided and the target method is not defined, the default is returned. This shows that ad hoc polymorphism still respects method overriding.
Now that I've introduced the concept of ad hoc polymorphism from a pseudocode point of view and through a real language (Python) that supports it directly, let's look at adding this capability to Java. Consider the library of functions defined in ReflectionUtilities.java. All the methods are public.
The call (for void methods) or invoke (for methods other than void) methods use ad hoc polymorphism. The ...Static forms invoke static methods, the others call instance methods. Although not shown in Listing Nine, all the call and invoke methods throw InvokeException. For zero argument methods, the args parameter may be null or omitted. The def parameter provides a default value if the method cannot be found. (The curly braces "{" and "}" indicate an optional argument.) The canInvoke... and findMethod methods are used for testing if the target method can be called with the specified arguments. These methods (Listing Ten) are used to prevent InvokeExceptions for undefined methods.
To illustrate how you can use this library with Java, I use the inner class definitions in Listing Eleven (test code is contained in ReflectionUtilitiesTest.java) and the testit method definition in Listing Twelve. Listing Thirteen presents examples of invoking some String methods, while Listing Fourteen (as in the Python code) illustrates invoking methods on the sample Test... classes. The same comments apply.
How does all this use of reflection perform? In general, it slows down execution. Thus, this technique may not be appropriate for performance-critical code. Still, its flexibility can far outweigh this performance cost in most types of code, particularly with user-interface code.
You can see in Tables 1 and 2 that much of the lost time is in the method lookup. In the example code, I have used a simple linear search to find methods. As expected, this has poor performance. Many opportunities exist to cache found methods to significantly reduce this time. Also, you can find methods by using method findMethod in advance. This can be especially helpful if the method is going to be used frequently, such as in a loop.
I have included the results of some simple measures of this library. These measures were taken on an IBM ThinkPad T23 with an 1.3-GHz Intel Pentium III Mobile. This was using the Java 1.4.1_01 JRE. Each test performed two (nearly) identical actions per loop (so the time per action is 1/2 the time shown) and the loop was executed 100,000 times.
From the tables, it is clear that the Hot Spot optimizer can save lots of execution time. I noticed that the Reflection APIs caused lots of garbage collectionsthese times are included. The number of garbage collections appear to be the same with or without the optimizations.
In this article, I've shown how a simple library can be defined that will provide ad hoc polymorphism support for Java programmers. I've provided some examples of its use, and I've also compared this library with the native ad hoc polymorphism support in the Python language. Except for some slightly less desirable syntax that Java exhibits, the support is identical.
Java Reflection API tutorial (http://java.sun.com/docs/books/tutorial/reflect/).
Java Reflection API JavaDoc (http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ reflect/package-summary.html).
Java Reflection API code samples (http://developer.java.sun.com/developer/code samples/refl.html).
Python (http://www.python.org/).
Jython (Python running on a JVM) (http://www.jython.org/).
SmallTalk (http://www.smalltalk.org/).
DDJ
class Test1: def method1 (self): return "Test1" class Test2: def method1 (self): return "Test2" class Test3: def method2 (self): return "Test3" class Test4(Test1): def method2 (self): return "Test4" class Test5(Test1): def method1 (self): return "Test5"Back to article
def testit (name, receiver, func, default=None): o = None print print "trying", name try: o = func(receiver) except Exception, e: print "Exception:", e.__class__, '-', e if e.__class__ == AttributeError and default is not None: o = default print "result of", name, '=', o return oBack to article
if __name__ == "__main__": s = "This is a test string" print "For value:", sBack to article
testit('s.find("xxx")', s, lambda x: x.find("xxx")) testit('s.find("test")', s, lambda x: x.find("test")) testit('s.find("xxx","yyy")', s, lambda x: x.find("xxx", "yyy")) trying s.find("xxx") result of s.find("xxx") = -1 trying s.find("test") result of s.find("test") = 10 trying s.find("xxx","yyy") exception: exceptions.TypeError - 2nd arg can't be coerced to int result of s.find("xxx","yyy") = NoneBack to article
t = Test1() testit('t.method1()', t, lambda x: x.method1()) testit('t.method2()', t, lambda x: x.method2()) trying t.method1() result of t.method1() = Test1 trying t.method2() Exception: exceptions.AttributeError - method2 result of t.method2() = None t = Test2() testit('t.method1()', t, lambda x: x.method1()) testit('t.method2()', t, lambda x: x.method2()) trying t.method1() result of t.method1() = Test2 trying t.method2() Exception: exceptions.AttributeError - method2 result of t.method2() = NoneBack to article
t = Test3() testit('t.method1()', t, lambda x: x.method1()) testit('t.method2()', t, lambda x: x.method2()) trying t.method1() Exception: exceptions.AttributeError - method1 result of t.method1() = None trying t.method2() result of t.method2() = Test3Back to article
t = Test4() testit('t.method1()', t, lambda x: x.method1()) testit('t.method2()', t, lambda x: x.method2()) trying t.method1() result of t.method1() = Test1 trying t.method2() result of t.method2() = Test4Back to article
t = Test5() testit('t.method1()', t, lambda x: x.method1()) testit('t.method2()', t, lambda x: x.method2()) testit('t.method2()', t, lambda x: x.method2(), "default") trying t.method1() result of t.method1() = Test5 trying t.method2() Exception: exceptions.AttributeError - method2 result of t.method2() = None trying t.method2() Exception: exceptions.AttributeError - method2 result of t.method2() = defaultBack to article
void call(Object instance, String name, {Object[] args}); void callStatic(Class owner, String name, {Object[] args}); Object invoke(Object instance, String name, {Object[] args, {Object def}}); Object invokeStatic(Class owner, String name, {Object[] args, {Object def}});Back to article
boolean canInvoke(Object instance, String name, {Object[] args}); boolean canInvokeStatic(Class owner, String name, {Object[] args}); Method findMethod(Class owner, String name, {Object[] args});Back to article
class Test1 { public String method1() { return "Test1"; } } class Test2 { public String method1() { return "Test2"; } } class Test3 { public String method2() { return "Test3"; } } class Test4 extends Test1 { public String method2() { return "Test4"; } } class Test5 extends Test1 { public String method1() { return "Test5"; } }Back to article
Object testit(ReflectionUtilities ru, String msg, Object receiver, String name) { return testit(ru, msg, receiver, name, null); } Object testit(ReflectionUtilities ru, String msg, Object receiver, String name, Object[] args) { return testit(ru, msg, receiver, name, args, null); } Object testit(ReflectionUtilities ru, String msg, Object receiver, String name, Object[] args, Object def) { Object o = null; try { System.out.println(msg); o = ru.invoke(receiver, name, args, def); } catch (InvokeException e) { System.out.println(e.getMessage()); } System.out.println(msg + ": " + o); return o; }Back to article
ReflectionUtilities ru = ReflectionUtilities.getDefault(); String s = new String("This is a test string"); System.out.println("For value: " + s); For value: This is a test string testit(ru, "length()", s, "length"); testit(ru, "length(\"bad\")=-1", s, "length", new Object[] {"bad"}, new Integer(-1)); length() length(): 21 length("bad")=-1 length("bad")=-1: -1 testit(ru, "indexOf(\"test\")", s, "indexOf", new Object[] {"test"}); testit(ru, "indexOf(\"test\", 10)", s, "indexOf", new Object[] {"test", new Integer(10)}); testit(ru, "indexOf(\"test\", 1f)", s, "indexOf", new Object[] {"test", new Float(1f)}); testit(ru, "indexOf()", s, "indexOf", new Object[] {}); indexOf("test") indexOf("test"): 10 indexOf("test", 10) indexOf("test", 10): 10 indexOf("test", 1f) method not found - java.lang.String.indexOf(java.lang.String,java.lang.Float) indexOf("test", 1f): null indexOf() method not found - java.lang.String.indexOf() indexOf(): null testit(ru, "toUpperCase()", s, "toUpperCase"); toUpperCase() toUpperCase(): THIS IS A TEST STRING testit(ru,"startsWith(\"This\")", s,"startsWith", new Object[] {"This"}); testit(ru,"startsWith(\"That\")", s,"startsWith", new Object[] {"That"}); startsWith("This") startsWith("This"): true startsWith("That") startsWith("That"): falseBack to article
Object t; t = new Test1(); testit(ru, "t.method1()", t, "method1"); testit(ru, "t.method2()", t, "method2"); t.method1() t.method1(): Test1 t.method2() method not found - com.ibm.reflect.ReflectionUtilitiesTest$Test1.method2() t.method2(): null t = new Test2(); testit(ru, "t.method1()", t, "method1"); testit(ru, "t.method2()", t, "method2"); t.method1() t.method1(): Test2 t.method2() method not found - com.ibm.reflect.ReflectionUtiltiesTest$Test2.method2() t.method2(): null t = new Test3(); testit(ru, "t.method1()", t, "method1"); testit(ru, "t.method2()", t, "method2"); t.method1() method not found - com.ibm.reflect.ReflectionUtiltiesTest$Test3.method1() t.method1(): null t.method2() t.method2(): Test3 t = new Test4(); testit(ru, "t.method1()", t, "method1"); testit(ru, "t.method2()", t, "method2"); t.method1() t.method1(): Test1 t.method2() t.method2(): Test4 t = new Test5(); testit(ru, "t.method1()", t, "method1"); testit(ru, "t.method2()", t, "method2"); t.method1(): Test5 t.method2() method not found - com.ibm.reflect.ReflectionUtiltiesTest$Test5.method2() t.method2(): nullBack to article
(a)
class Bird { method fly(){ ... } } class Plane { method fly() { ... } } class Angel { method fly(){ ... } } class Leaf { method fly(){ ... } }(b)
flyer = new Bird() flyer.fly() flyer = new Plane() flyer.fly() flyer = new Angel() flyer.fly() flyer = new Leaf() flyer.fly()
Loop body | MicroSeconds/iteration | Comment |
int assignment | 0.500 | No allocations/iteration (curious that this is larger; may be collection effects). |
New Integer | 0.200 | Two allocations/iteration assignment. |
String assignment | 0.100 | No allocations/iteration. |
Direct call | 0.100 | Using Java reflection API directly. |
ReflectionUtilities.findMethod | 9.710 | Represents 94% of total call time. |
Method.invoke | 0.610 | Invoking a prefound method. |
ReflectionUtilities.invoke | 10.310 |
Loop body | MicroSeconds/iteration | Comment |
int assignment | 0.100 | |
new Integer assignment | 0.800 | |
String assignment | 0.100 | |
Direct call | 0.300 | |
ReflectionUtilities.findMethod | 47.870 | Represents 89% of total call time. |
Method.invoke | 2.500 | |
ReflectionUtilities.invoke | 53.580 |
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.