在寻找正确的方法时,Java 不会考虑方法参数的运行时类型。Java 只是简单地解释变量类型而不是值。
解决方案:
Java 6 注解可用于注解方法并实现多方法和值分派。所有这些都可以在运行时完成,不需要任何特殊的编译或预处理,并且使用仍然可以合理地方便用户使用。
我们需要引入两个“简单”的注解来进行注解:
Methods
: 这个方法实现了什么多方法?
Parameters
: 我们应该派送什么价值?
然后,我们可以处理注释并构建实现特定多方法的方法列表。此列表需要排序,以便最具体的方法排在第一位。“最具体”是指对于每个方法参数(从左到右),参数类型/值更加专业(例如,它是子类或与指定值匹配)。调用多方法意味着调用最具体的适用方法。“适用”意味着方法原型与实际运行时参数匹配,“最具体”意味着我们可以简单地搜索排序列表并找到第一个适用的。
注释处理可以封装在一个类中,然后可以在用户定义的方法中使用,该方法将简单地使用实际运行时参数调用多方法调度代码。
执行
Multi接口实现了一个运行时方法注解,用于标记多方法:
package jmultimethod;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Multi {
public String value();
}
接口V实现了一个运行时参数注解,用于指定调度值:
package jmultimethod;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface V {
public String value();
}
多方法代码如下:
package jmultimethod;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Multimethod {
protected String name;
protected final ArrayList<Method> methods = new ArrayList<Method>();
protected final MethodComparator methodComparator = new MethodComparator();
public Multimethod(String name, Class... classes) {
this.name = name;
for(Class c: classes) {
add(c);
}
}
public void add(Class c) {
for(Method m: c.getMethods()) {
for(Annotation ma: m.getAnnotations()) {
if(ma instanceof Multi) {
Multi g = (Multi) ma;
if(this.name.equals(g.value())) {
methods.add(m);
}
}
}
}
sort();
}
protected void sort() {
Method[] a = new Method[methods.size()];
methods.toArray(a);
Arrays.sort(a, methodComparator);
methods.clear();
for(Method m: a) {
methods.add(m);
}
}
protected class MethodComparator implements Comparator<Method> {
@Override
public int compare(Method l, Method r) {
// most specific methods first
Class[] lc = l.getParameterTypes();
Class[] rc = r.getParameterTypes();
for(int i = 0; i < lc.length; i++) {
String lv = value(l, i);
String rv = value(r, i);
if(lv == null) {
if(rv != null) {
return 1;
}
}
if(lc[i].isAssignableFrom(rc[i])) {
return 1;
}
}
return -1;
}
}
protected String value(Method method, int arg) {
Annotation[] a = method.getParameterAnnotations()[arg];
for(Annotation p: a) {
if(p instanceof V) {
V v = (V) p;
return v.value();
}
}
return null;
}
protected boolean isApplicable(Method method, Object... args) {
Class[] c = method.getParameterTypes();
for(int i = 0; i < c.length; i++) {
// must be instanceof and equal to annotated value if present
if(c[i].isInstance(args[i])) {
String v = value(method, i);
if(v != null && !v.equals(args[i])) {
return false;
}
} else {
if(args[i] != null || !Object.class.equals(c[i])) {
return false;
}
}
}
return true;
}
public Object invoke(Object self, Object... args) {
Method m = null; // first applicable method (most specific)
for(Method method: methods) {
if(isApplicable(method, args)) {
m = method;
break;
}
}
if(m == null) {
throw new RuntimeException("No applicable method '" + name + "'.");
}
try {
return m.invoke(self, args);
} catch (Exception e) {
throw new RuntimeException("Method invocation failed '" + name + "'.");
}
}
}
要使用多方法,用户代码必须:
使用多方法名称注释方法,例如
@Multi("myMultimethod")
注释方法的名称可以是 Java 喜欢的任何名称。它很可能与多方法名称不同,因为某些方法可能具有足够相似的原型,从而导致 Java 编译器的名称冲突(并且可能是因为编译器可能会遇到空值问题)。此外,该方法应该对 Multimethod 类是可见的(例如,公共的)。
通过创建 Multimethod 对象来处理注释,例如
protected Multimethod mm = new Multimethod("myMultimethod", getClass());
根据需要使用通用参数定义多方法“入口点”方法。该方法使用上面创建的 Multimethod 对象进行调度,例如
public void myMultimethod(Object X, Object Y) {
mm.invoke(this, X, Y);
}
然后,可以像任何普通的 Java 方法一样调用多方法,例如
我的多方法(1,空);
限制:
值分派仅适用于 Java 注释支持的值,例如 String 类型的值。
以下代码基于 Multiple Dispatch 示例
package jmultimethod;
public class AsteroidTest {
class Asteroid {}
class Spaceship {}
@Multi("collide")
public void collideOO(Object X, Object Y) {
log("?? Bang, what happened? ", X, Y);
}
@Multi("collide")
public void collideAA(Asteroid X, Asteroid Y) {
log("AA Look at the beautiful fireworks! ", X, Y);
}
@Multi("collide")
public void collideAS(Asteroid X, Spaceship Y) {
log("AS Is it fatal? ", X, Y);
}
@Multi("collide")
public void collideSA(Spaceship X, Asteroid Y) {
log("SA Is it fatal? ", X, Y);
}
@Multi("collide")
public void collideSS(Spaceship X, Spaceship Y) {
log("SS Who's fault was it? ", X, Y);
}
@Multi("collide")
public void collide1S(String X, Spaceship Y) {
log("1S any string? ", X, Y);
}
@Multi("collide")
public void collide2S(@V("hi") String X, Spaceship Y) {
log("2S 'hi' value? ", X, Y);
}
protected Multimethod mm = new Multimethod("collide", getClass());
public void collide(Object X, Object Y) {
mm.invoke(this, X, Y);
}
public void run() {
Object A = new Asteroid();
Object S = new Spaceship();
collide(A, A);
collide(A, S);
collide(S, A);
collide(S, S);
collide(A, 1);
collide(2, A);
collide(S, 3);
collide(4, S);
collide(5, null);
collide(null, null);
collide("hi", S);
collide("hello", S);
}
public void log(Object... args) {
for(Object o: args) {
if(o instanceof String) {
System.out.print(" " + (String) o);
} else {
System.out.print(" " + o);
}
}
System.out.println();
}
public static void main(String[] args) throws Exception {
AsteroidTest t = new AsteroidTest();
t.run();
}
}
程序输出(部分编辑以适应屏幕)是:
AA Look at the beautiful fireworks! Asteroid@1f24bbbf Asteroid@1f24bbbf
AS Is it fatal? Asteroid@1f24bbbf Spaceship@24a20892
SA Is it fatal? Spaceship@24a20892 Asteroid@1f24bbbf
SS Who's fault was it? Spaceship@24a20892 Spaceship@24a20892
?? Bang, what happened? Asteroid@1f24bbbf 1
?? Bang, what happened? 2 Asteroid@1f24bbbf
?? Bang, what happened? Spaceship@24a20892 3
?? Bang, what happened? 4 Spaceship@24a20892
?? Bang, what happened? 5 null
?? Bang, what happened? null null
2S 'hi' value? hi Spaceship@24a20892
1S any string? hello Spaceship@24a20892