使用 MethodHandle 查找最具体的重载方法
Posted
技术标签:
【中文标题】使用 MethodHandle 查找最具体的重载方法【英文标题】:Finding most specific overloaded method using MethodHandle 【发布时间】:2017-02-26 08:35:53 【问题描述】:假设我在给定类型(类/接口)中有三个方法:
public void foo(Integer integer);
public void foo(Number number);
public void foo(Object object);
使用MethodHandle
或反射,我想为仅在运行时知道类型的对象找到最具体的重载方法。即我想在运行时做JLS 15.12。
例如,假设我在包含这三个方法的上述类型的方法中有以下内容:
Object object = getLong(); // runtime type is Long *just an example*
MethodHandles.lookup()
.bind(this, "foo", methodType(Void.class, object.getClass()))
.invoke(object);
然后我在概念上希望选择foo(Number number)
,但上面会抛出异常,因为API 只会寻找foo(Long)
方法而没有别的。 注意Long
的用法只是一个例子。对象的类型实际上可以是任何东西; String、MyBar、Integer、...等等等。
MethodHandle API 中是否有某些东西在运行时自动执行与编译器在 JLS 15.12 之后执行的相同类型的解析?
【问题讨论】:
为什么不把object.getClass()
改成Number.class
?
@TimothyTruckle 因为代码是通用的,不知道任何Number.class
。 Object
可以是任何类型,Long
只是一个示例。
这里的问题是“重载方法”在编译时得到解决,而反射发生在运行时。因此,您必须自己找到合适的方法。也许您应该遍历 objects 方法并测试参数是否是 object
的 Parent ...
@TimothyTruckle 这几乎是整个问题,是否已经存在某些东西,这样我就不必自己重新实现 JLS 15.12。
@AR.3 sn-p 就是一个例子。一个实际用例是,对于 JSR 375,有一个 CDI bean 实现了采用通用凭据的 IdentityStore 接口。该实现可以处理任何类型的凭据。由于在这种情况下重载不起作用(代理对象上只有 IdentityStore 接口),因此实现者必须一直向下转换。为了消除这种需要,我想在执行此操作的接口中提供一个默认方法,但当前的方法只能找到完全匹配,而不是编译时重载(又名 JLS 15.12)那样的最佳匹配。
【参考方案1】:
基本上我搜索了所有可以使用一组参数执行的方法。因此,我按照 parameterType 与 methodParameterType 之间的距离对它们进行了排序。这样做,我可以得到最具体的重载方法。
测试:
@Test
public void test() throws Throwable
Object object = 1;
Foo foo = new Foo();
MethodExecutor.execute(foo, "foo", Void.class, object);
Foo:
class Foo
public void foo(Integer integer)
System.out.println("integer");
public void foo(Number number)
System.out.println("number");
public void foo(Object object)
System.out.println("object");
MethodExecutor:
public class MethodExecutor
private static final Map<Class<?>, Class<?>> equivalentTypeMap = new HashMap<>(18);
static
equivalentTypeMap.put(boolean.class, Boolean.class);
equivalentTypeMap.put(byte.class, Byte.class);
equivalentTypeMap.put(char.class, Character.class);
equivalentTypeMap.put(float.class, Float.class);
equivalentTypeMap.put(int.class, Integer.class);
equivalentTypeMap.put(long.class, Long.class);
equivalentTypeMap.put(short.class, Short.class);
equivalentTypeMap.put(double.class, Double.class);
equivalentTypeMap.put(void.class, Void.class);
equivalentTypeMap.put(Boolean.class, boolean.class);
equivalentTypeMap.put(Byte.class, byte.class);
equivalentTypeMap.put(Character.class, char.class);
equivalentTypeMap.put(Float.class, float.class);
equivalentTypeMap.put(Integer.class, int.class);
equivalentTypeMap.put(Long.class, long.class);
equivalentTypeMap.put(Short.class, short.class);
equivalentTypeMap.put(Double.class, double.class);
equivalentTypeMap.put(Void.class, void.class);
public static <T> T execute(Object instance, String methodName, Class<T> returnType, Object ...parameters) throws InvocationTargetException, IllegalAccessException
List<Method> compatiblesMethods = getCompatiblesMethods(instance, methodName, returnType, parameters);
Method mostSpecificOverloaded = getMostSpecificOverLoaded(compatiblesMethods, parameters);
//noinspection unchecked
return (T) mostSpecificOverloaded.invoke(instance, parameters);
private static List<Method> getCompatiblesMethods(Object instance, String methodName, Class<?> returnType, Object[] parameters)
Class<?> clazz = instance.getClass();
Method[] methods = clazz.getMethods();
List<Method> compatiblesMethods = new ArrayList<>();
outerFor:
for(Method method : methods)
if(!method.getName().equals(methodName))
continue;
Class<?> methodReturnType = method.getReturnType();
if(!canBeCast(returnType, methodReturnType))
continue;
Class<?>[] methodParametersType = method.getParameterTypes();
if(methodParametersType.length != parameters.length)
continue;
for(int i = 0; i < methodParametersType.length; i++)
if(!canBeCast(parameters[i].getClass(), methodParametersType[i]))
continue outerFor;
compatiblesMethods.add(method);
if(compatiblesMethods.size() == 0)
throw new IllegalArgumentException("Cannot find method.");
return compatiblesMethods;
private static Method getMostSpecificOverLoaded(List<Method> compatiblesMethods, Object[] parameters)
Method mostSpecificOverloaded = compatiblesMethods.get(0);
int lastMethodScore = calculateMethodScore(mostSpecificOverloaded, parameters);
for(int i = 1; i < compatiblesMethods.size(); i++)
Method method = compatiblesMethods.get(i);
int currentMethodScore = calculateMethodScore(method, parameters);
if(lastMethodScore > currentMethodScore)
mostSpecificOverloaded = method;
lastMethodScore = currentMethodScore;
return mostSpecificOverloaded;
private static int calculateMethodScore(Method method, Object... parameters)
int score = 0;
Class<?>[] methodParametersType = method.getParameterTypes();
for(int i = 0; i < parameters.length; i++)
Class<?> methodParameterType = methodParametersType[i];
if(methodParameterType.isPrimitive())
methodParameterType = getEquivalentType(methodParameterType);
Class<?> parameterType = parameters[i].getClass();
score += distanceBetweenClasses(parameterType, methodParameterType);
return score;
private static int distanceBetweenClasses(Class<?> clazz, Class<?> superClazz)
return distanceFromObjectClass(clazz) - distanceFromObjectClass(superClazz);
private static int distanceFromObjectClass(Class<?> clazz)
int distance = 0;
while(!clazz.equals(Object.class))
distance++;
clazz = clazz.getSuperclass();
return distance;
private static boolean canBeCast(Class<?> fromClass, Class<?> toClass)
if (canBeRawCast(fromClass, toClass))
return true;
Class<?> equivalentFromClass = getEquivalentType(fromClass);
return equivalentFromClass != null && canBeRawCast(equivalentFromClass, toClass);
private static boolean canBeRawCast(Class<?> fromClass, Class<?> toClass)
return fromClass.equals(toClass) || !toClass.isPrimitive() && toClass.isAssignableFrom(fromClass);
private static Class<?> getEquivalentType(Class<?> type)
return equivalentTypeMap.get(type);
当然可以通过一些重构和 cmets 来改进它。
【讨论】:
这绝对是非常有用的,登陆这个问题的人肯定会想复制你的代码,但问题实际上是关于 MethodHandle API 中是否有东西可以做类似的事情 没有有效地重新实现 JLS 15.12。答案可能很简单:“不,真的没有”。我主要想知道标准 API 中是否没有我完全缺少的东西。【参考方案2】:我找不到使用MethodHandle
s 的方法,但是有一个有趣的java.beans.Statement
实现了根据Javadocs 查找JLS 最具体的方法:
execute
方法查找名称与methodName
属性相同的方法,并在目标上调用该方法。 当目标类定义了许多具有给定名称的方法时,实现应使用 Java 语言规范 (15.11) 中指定的算法选择最具体的方法。
要检索Method
本身,我们可以使用反射来实现。这是一个工作示例:
import java.beans.Statement;
import java.lang.reflect.Method;
public class ExecuteMostSpecificExample
public static void main(String[] args) throws Exception
ExecuteMostSpecificExample e = new ExecuteMostSpecificExample();
e.process();
public void process() throws Exception
Object object = getLong();
Statement s = new Statement(this, "foo", new Object[] object );
Method findMethod = s.getClass().getDeclaredMethod("getMethod", Class.class,
String.class, Class[].class);
findMethod.setAccessible(true);
Method mostSpecificMethod = (Method) findMethod.invoke(null, this.getClass(),
"foo", new Class[] object.getClass() );
mostSpecificMethod.invoke(this, object);
private Object getLong()
return new Long(3L);
public void foo(Integer integer)
System.out.println("Integer");
public void foo(Number number)
System.out.println("Number");
public void foo(Object object)
System.out.println("Object");
【讨论】:
我可以知道投反对票的原因吗? 不知道,对我来说看起来是合法的。尽管使用MethodFinder
调用的Statement. getMethod
方法会更干净一些,并且两者都使用内部API(例如sun.misc.Unsafe
,它将被隔离)。但我猜Statement.execute
是主题启动者正在寻找的东西,它存在于标准库中是令人惊奇的事情。谢谢
这段代码的味道是通过反射访问私有静态getMethod
,但如果用户only打算调用该方法,则不需要这样做,只需要在Statement
对象上调用execute()
即可。
如果使用java.beans.Expression
,你甚至可以获得调用的结果(Statement
不能)
@manouti 你不应该把最重要的一点埋在评论里。您应该建议首先调用execute
,然后可能会在(且仅当)需要检索Method
时添加替代方案,但在答案中也提到缺点,即调用私有API不保证在那里。更不用说对较新 Java 版本的更强封装(公平地说,当您编写答案时,它并不存在)。【参考方案3】:
你可以使用MethodFinder.findMethod()
来实现它。
@Test
public void test() throws Exception
Foo foo = new Foo();
Object object = 3L;
Method method = MethodFinder.findMethod(Foo.class, "foo", object.getClass());
method.invoke(foo, object);
public static class Foo
public void foo(Integer integer)
System.out.println("integer");
public void foo(Number number)
System.out.println("number");
public void foo(Object object)
System.out.println("object");
由于在java根库中,所以遵循JLS 15.12。
【讨论】:
这基本上就是我在下面回答的内容,只是您使用的是内部com.sun
类。这将在 Eclipse 等 IDE 中显示警告。
好发现!然而,可悲的是,这种方法并不是真正的标准。特别是,这在 Java 9 中不起作用。因此,最好的策略是从 OpenJDK 中获取 MethodFinder/AbstractFinder 的基本部分(如果 GPLv2 条款适合您的情况)。
不幸的是 MethodFinder 不遵循 JLS 15.12。特别是“java.lang.NoSuchMethodException: Ambiguous methods are found”在某些情况下会抛出空参数,而这些情况由 Java 编译器正确处理。【参考方案4】:
不,我在 MethodHandle API 中没有看到类似的东西。 commons-beanutils
和 MethodUtils#getMatchingAccessibleMethod
中存在类似的东西,所以你不必实现它。
它看起来像这样:
Object object = getLong();
Method method = MethodUtils.getMatchingAccessibleMethod(this.getClass(), "foo", object.getClass());
您可以转换为 MethodHandle API 或直接使用 Method
:
MethodHandle handle = MethodHandles.lookup().unreflect(method);
handle.invoke(this, object);
【讨论】:
从评论到getMatchingAccessibleMethod
:这个方法有点不确定,因为它循环遍历方法名称并返回第一个匹配的方法。 :D【参考方案5】:
考虑到以下约束:a)参数的类型只在运行时知道,b)只有一个参数,一个简单的解决方案可以是遍历类层次结构并扫描已实现的接口,如下例所示。
public class FindBestMethodMatch
public Method bestMatch(Object obj) throws SecurityException, NoSuchMethodException
Class<?> superClss = obj.getClass();
// First look for an exact match or a match in a superclass
while(!superClss.getName().equals("java.lang.Object"))
try
return getClass().getMethod("foo", superClss);
catch (NoSuchMethodException e)
superClss = superClss.getSuperclass();
// Next look for a match in an implemented interface
for (Class<?> intrface : obj.getClass().getInterfaces())
try
return getClass().getMethod("foo", intrface);
catch (NoSuchMethodException e)
// Last pick the method receiving Object as parameter if exists
try
return getClass().getMethod("foo", Object.class);
catch (NoSuchMethodException e)
throw new NoSuchMethodException("Method not found");
// Candidate methods
public void foo(Map<String,String> map) System.out.println("executed Map");
public void foo(Integer integer) System.out.println("executed Integer");
public void foo(BigDecimal number) System.out.println("executed BigDecimal");
public void foo(Number number) System.out.println("executed Number");
public void foo(Object object) System.out.println("executed Object");
// Test if it works
public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
FindBestMethodMatch t = new FindBestMethodMatch();
Object param = new Long(0);
Method m = t.bestMatch(param);
System.out.println("matched " + m.getParameterTypes()[0].getName());
m.invoke(t, param);
param = new HashMap<String,String>();
m = t.bestMatch(param);
m.invoke(t, param);
System.out.println("matched " + m.getParameterTypes()[0].getName());
【讨论】:
以上是关于使用 MethodHandle 查找最具体的重载方法的主要内容,如果未能解决你的问题,请参考以下文章
Gradle Android 错误:MethodHandle.invoke 和 MethodHandle.invokeExact
错误:仅从 Android O 开始支持 MethodHandle.invoke 和 MethodHandle.invokeExact (--min-api 26)
Android studio“MethodHandle.invoke 和 MethodHandle.invokeExact 仅支持从 Android O (--min-api 26) 开始”构建 AP