Java Reflection & Smalltalk-Like Method Dispatching

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

July, 2004: Java Reflection & Smalltalk-Like Method Dispatching

Ad hoc polymorphism support for Java programmers

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 classes—Bird, 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 trivial—you 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.

Ad Hoc Polymorphism in Python

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 features—lambda (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.

Ad Hoc Polymorphism in Java

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.

Using the Library

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.

What About Performance?

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 collections—these times are included. The number of garbage collections appear to be the same with or without the optimizations.

Conclusion

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.

References

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



Listing One

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


Listing Two
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 o
Back to article


Listing Three
if __name__ == "__main__":
    s = "This is a test string"
    print "For value:", s
Back to article


Listing Four
    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") = None
Back to article


Listing Five
    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() = None
Back to article


Listing Six
    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() = Test3
Back to article


Listing Seven
    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() = Test4
Back to article


Listing Eight
    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() = default
Back to article


Listing Nine
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


Listing Ten
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


Listing Eleven
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


Listing Twelve
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


Listing Thirteen
    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"): false
Back to article


Listing Fourteen
    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(): null
Back to article

July, 2004: Java Reflection & Smalltalk-Like Method Dispatching

(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()

Example 1: (a) Using these class definitions (in pseudocode), all of the method calls in Example 1(b) are valid.

July, 2004: Java Reflection & Smalltalk-Like Method Dispatching

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

Table 1: With Hot Spot optimization.

July, 2004: Java Reflection & Smalltalk-Like Method Dispatching

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

Table 2: With the interpreter.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.