假设我在给定类型(类/接口)中有三个方法:
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之后的编译器相同的分辨率?
答案 0 :(得分:8)
基本上我搜索了所有可以用一组参数执行的方法。所以,我按照parameterType和methodParameterType之间的距离对它们进行了排序。这样做,我可以获得最具体的重载方法。
测试:
@Test
public void test() throws Throwable {
Object object = 1;
Foo foo = new Foo();
MethodExecutor.execute(foo, "foo", Void.class, object);
}
The 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);
}
}
当然,可以通过一些重构和评论来改进它。
答案 1 :(得分:6)
我找不到使用MethodHandle
执行此操作的方法,但有一个有趣的java.beans.Statement
根据Javadocs实现查找JLS最具体的方法:< / p>
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");
}
}
答案 2 :(得分:4)
您可以使用import pip
pip.main(["install", "MyPackage"])
来实现它。
MethodFinder.findMethod()
因为它在java根库中,所以它遵循JLS 15.12。
答案 3 :(得分:3)
No, I haven't seen anything like that in MethodHandle API. Similar thing exists in commons-beanutils
as MethodUtils#getMatchingAccessibleMethod
so you don't have to implement that.
It will look something like this:
Object object = getLong();
Method method = MethodUtils.getMatchingAccessibleMethod(this.getClass(), "foo", object.getClass());
You can convert to MethodHandle API or just use the Method
directly:
MethodHandle handle = MethodHandles.lookup().unreflect(method);
handle.invoke(this, object);
答案 4 :(得分:0)
鉴于以下约束: 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());
}
}