mm4j - Simple multi-methods for Java
Provides dynamic dispatch on all method parameters, also known as multi-methods. This is useful, for instance, when implementing event-driven code or structuring a program explicitly as a state-machine. It is likely that we'll have something like this natively in future versions of Java supported by the new invokedynamic opcode.
The latest version of the single implementation file MultiMethod.java and of this document are available at the mm4j homepage. Check the change log for the latest news.
This implementation builds on reflection capabilities in the Java language. First, an exact match is attempted. If it does not exist, super-classes of parameter values are also tested, starting from right to left. Currently, interfaces are not considered, only super-classes. Matched methods are cached and reused thus avoiding that the recursive matching procedure is called repeatedly. This results in a fairly efficient implementation which on the average adds only a single map lookup to the cost of normal method invocation.
Super-classes of right-most parameters are tested first, thus naturally accommodating the native dynamic dispatch on this as the left-most parameter. To better understand searching order, consider the following sample program:
import java.lang.reflect.*;
import mm4j.*;
public class Test {
public void m(String a, Integer b) {
System.out.println("m(String,Integer) with "+
a.getClass().getName()+" "+b.getClass().getName());
}
public void m(Object a, Integer b) {
System.out.println("m(Object,Integer) with "+
a.getClass().getName()+" "+b.getClass().getName());
}
public void m(String a, Object b) {
System.out.println("m(String,Object) with "+
a.getClass().getName()+" "+b.getClass().getName());
}
public void m(Object a, Object b) {
System.out.println("m(Object,Object) with "+
a.getClass().getName()+" "+b.getClass().getName());
}
public static void main(String[] args) {
try {
Test t=new Test();
MultiMethod mm=MultiMethod.getMultiMethod(t.getClass(), "m");
mm.invoke(t, "a", 2);
mm.invoke(t, "a", "b");
mm.invoke(t, 1, "b");
mm.invoke(t, 1,2);
Method m=mm.resolve(String.class, Object.class);
m.invoke(t, "a", 1);
m=mm.resolve(String.class, String.class);
m.invoke(t, "a", 1);
} catch(Exception e) {e.printStackTrace();}
}
};
Notice that it uses the auto-boxing in Java5 for integers.
The sample program should produce the following output:
m(String,Integer) with java.lang.String java.lang.Integer m(String,Object) with java.lang.String java.lang.String m(Object,Object) with java.lang.Integer java.lang.String m(Object,Object) with java.lang.Integer java.lang.Integer m(String,Object) with java.lang.String java.lang.Integer m(String,Object) with java.lang.String java.lang.IntegerContrast the result of the second invocation, which has (String, String) parameters and matches (String, Object), with the result of the fourth invocation, which has (Integer, Integer) parameters but matches (Object, Object) and not (Object, Integer).
A particularly interesting idiom is achieved by using a Java5 variable parameter list to wrap dynamic invocation and exception handling as follows:
import java.lang.reflect.*;
import java.io.*;
import mm4j.*;
interface Idiom {
public void m(Object...) throws NoSuchMethodException, IOException;
}
public class IdiomImpl implements Idiom {
// Alternatives
public void m(Integer a) { }
public void m(String a, Object b) throws IOException { }
public void m(Integer a, String b, Object c) { }
// Wrapper
public void m(Object... args) throws NoSuchMethodException, IOException {
try {
mm.invoke(this, args);
} catch(InvocationTargetException e) {
Throwable target=e.getTargetException();
if (target instanceof IOException)
throw (IOException)target;
else if (target instanceof RuntimeException)
throw (RuntimeException)target;
target.printStackTrace();
} catch(IllegalAccessException e) {
e.printStackTrace();
}
}
private static MultiMethod mm=MultiMethod.getMultiMethod(IdiomImpl.class, "m");
// Sample usage
public static void main(String[] args) {
try {
Idiom t=new IdiomImpl();
t.m(3);
t.m("a", "b");
t.m(1, "b", 3f);
} catch(Exception e) {e.printStackTrace();}
}
};
Most of the effort is related to unwrapping InvocationTargetExceptions
to each of the possibilities. The resulting client code, in main, is however
very user friendly.
mm4j.MultiMethod - Simple multi-methods for Java Copyright (C) 2005 Jose Orlando Pereira <jop@di.uminho.pt> All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the University of Minho nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Changes:
- 20051125 - Bug fix for null pointer exceptions in weak hash maps.
- 20051115 - Initial public release.
| Method Summary | |
|---|---|
Class |
getDeclaringClass()
Returns the Class object representing the class or interface that declares the method family represented by this MultiMethod object. |
static MultiMethod |
getMultiMethod(Class claz,
String name)
Lookup a dynamic method in a specific class. |
String |
getName()
Returns the name of the target methods as a String. |
Object |
invoke(Object obj,
Object... args)
Invokes the method represented by this MultiMethod object that better fits the specified parameters on the specified object. |
java.lang.reflect.Method |
resolve(Class... types)
Resolves the method represented by this MultiMethod object that better fits the specified parameters types. |
String |
toString()
|
| Methods inherited from class Object |
|---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait |
| Method Detail |
|---|
getMultiMethod
public static MultiMethod getMultiMethod(Class claz, String name)
- Lookup a dynamic method in a specific class. This method can
be used for invocations on instances of any derived class.
No validation is performed at this time and therefore there is
no guarantee that invocations will succeed, not even that there
is a method with the same name.
- Parameters:
claz- a class, or superclass, of the target objectsname- the name of the target method- Returns:
- a dynamic method object
invoke
public final Object invoke(Object obj,
Object... args)
throws IllegalAccessException,
NoSuchMethodException,
java.lang.reflect.InvocationTargetException
- Invokes the method represented by this MultiMethod object that
better fits the specified parameters on the specified object. The
same effect can be achieved by first resolving the method and
then using invoke. Note that this method cannot be used to invoke
target methods with primitive or null arguments.
- Parameters:
obj- the target object instanceargs- the argument values- Returns:
- the return value of the invoked method
- Throws:
NoSuchMethodException- no matching method foundIllegalAccessException- matching method exists but cannot be accessedjava.lang.reflect.InvocationTargetException- nested exception by target
resolve
public final java.lang.reflect.Method resolve(Class... types)
throws NoSuchMethodException
- Resolves the method represented by this MultiMethod object that
better fits the specified parameters types. The result can be
used repeatedly to avoid the overhead of matching. This method is
also able to resolve methods declared with primitive
arguments. The resulting method can also be invoked with
null arguments.
- Parameters:
types- the classes of the argument of the method to be searched- Returns:
- a specific Method object
- Throws:
NoSuchMethodException- no matching method found
getDeclaringClass
public Class getDeclaringClass()
- Returns the Class object representing the class or interface
that declares the method family represented by this MultiMethod
object.
- Returns:
- the declaring class
getName
public String getName()
- Returns the name of the target methods as a String.
- Returns:
- the name of the method
toString
public String toString()
- Overrides:
toStringin classObject