Introduction | Example | Documentation | Download | Links | Contact |
---|
The Runabout is an extension of the Java libraries that adds two-argument multi-dispatch to Java without changing the language or the VM. Like the Walkabout, the Runabout uses reflection to find visit methods. But instead of invoking the visit methods with reflection, the Runabout uses dynamic code generation to create code at run-time that will invoke the appropriate visit method. This puts the Runabout closer to MultiJava, a Java source compiler that compiles Java with multi-methods to ordinary Java bytecode. Unlike MultiJava, the Runabout runs when the application is executed, and not at compile time. Writing code with the runabout is very similar to writing visitors or multi methods with MultiJava.
The dispatch in the runabout is a factor of 3 slower than ordinary uni- or double dispatch (on Sun's JDK 1.4.1 for 10 million invocations). Detailed performance numbers are in my ECOOP 2003 paper.
import org.grothoff.Runabout; public class Example { public static void main(String[] args) { MyRunabout mr = new MyRunabout(); Object[] objects = new Object[] { "Hello World", new A(), new B() }; for (int i=0;i < objects.length;i++) mr.visitAppropriate(objects[i]); if (mr.counter != 7) throw new Error("This does not happen."); } public static class MyRunabout extends Runabout { int counter = 0; public void visit(String s) { if (s == "Hello World") counter = 1; } public void visit(A a) { counter+=2; } public void visit(I i) { counter+=4; } } public static class A {} public interface I {} public static class B implements I {} }
Using the Runabout is quite straightforward. You download the JAR-file and add it to the classpath of your Java application. Then, you create public classes that extend Runabout. In these classes, you place visit methods that return void and take one argument which must also be of a public type. The Runabout then allows you to invoke the visit method that is the best match for a given object using visitAppropriate. The semantics for best match are equivalent to overloading of methods in Java, only that the Runabout uses the dynamic type, where overloading uses the static type.
A common example for Java code that asks for refactoring with a Runabout looks like this:
class Example2 { void run(Object a) { if (a instanceof A) foo((A)a); else if (a instanceof B) bar((B)b); else if (a instanceof C) ... } }
Sometimes, a visitor can be a good solution here, but if for example A is String or any other legacy code that can not be given an accept method, this is not possible. With the Runabout, the code can be cleaned up with a result like this:
public class Example2 extends Runabout { public void visit(A a) { foo(a); } public void visit(B b) { bar(b); } public void visit(C c) { ... } // ... void run(Object x) { this.visitAppropriate(x); } }
Another case is are visitors that are getting too complex...
interface I { void accept(MyVisitor m); } static class A implements I { void accept(MyVisitor m) { m.visit(this); } } static class B extends A { void accept(MyVisitor m) { m.visit(this); } } static class C { void accept(MyVisitor m) { m.visit(this); } } interface MyVisitor { visit(I i); visit(A i); visit(B i); visit(C i); // ... } class MyIABVisitor implements MyVisitor { void visit(I i) { // ... } void visit(A a) { visit((I)a); } void visit(B b) { visit((A)b); } void visit(C c) { throw new Error("IAB Visitor only works for I, A and B"); } // ... } class MyIBCVisitor implements MyVisitor { void visit(I i) { // ... } void visit(A a) { throw new Error("IBC Visitor only works for I, B and C"); } void visit(B b) { // ... } void visit(C c) { // ok here } // ... }
This example shows two issues that can make visitors
compilicated. irst, if the set of visitee types (here I, A, B
and C) forms some type of hierarchy, visit calls that merely
indirect to other visit methods can become common. If the
hierarchy changes frequently and/or is huge, writing and
maintaining these simple indirection calls can be
troublesome. Also, if the visitee types often is only partially
applicable, say some visitors operate on the subset {I, A, B}
and others operate only on {I, B, C}, visitors can have the
problem that either an accept method and a specific
base-type is required for every set or that an excepting visit
method must be placed in every visitor that does not expect to
visit that specific type.
In both cases, the Runabout avoids these problems. Client code
only needs to define the visit methods that are used and
indirections to other visit methods also do not need to be
specified in most cases.
public interface I {} public static class A implements I {} public static class B extends A {} public static class C {} public class MyIABVisitor extends Runabout { public void visit(I i) { // covers all subtypes of I! // ... } public void visitDefault(Object o) { throw new Error("IAB Visitor only works for I, A and B"); } } public class MyIBCVisitor extends Runabout { public void visit(I i) { // ... } public void visitDefault(Object o) { throw new Error("IBC Visitor only works for I, B and C"); } public void visit(B b) { // ... } public void visit(C c) { // ok here } // ... }
Note that the visitDefault methods are not even necessary in this example (unless a custom error-message is desired) since the Runabout would throw an Exception by default anyway.