最近在写一个项目,里面需要频繁使用反射操作。由于Java的反射API使用起来比较复杂,所以我决定把常用的反射操作封装成一个工具类:ReflectUtils
。
在ReflectUtils
中,有这么一个call
方法:
public static <T> T call(Object obj, String methodName, Object... params);
这个方法利用反射调用某个实例对象的某个方法,obj
是对象实例,methodName
是方法名,params
是传递给方法的参数。
最初这个方法是这么来实现的:
public static <T> T call(Object obj, String methodName, Object... params){ try { Method method = obj.getClass().getMethod(methodName, getTypes(params)); return (T) method.invoke(obj, params); } catch (Exception e) { throw new RuntimeException(e); }}
这个实现看起来很简单,只是把反射获取方法和调用方法的过程简单封装了一下,其中getTypes
方法用于获取参数类型列表:
private static Class<?>[] getTypes(Object... params){ return Arrays.stream(params).map(Object::getClass).toArray(Class<?>[]::new);}
不过很快就发现了问题。假设我要调用某个String
对象的substring
方法:
String s1 = "hello";String s2 = ReflectUtils.call(s1, "substring", 1, 4);
预期s2
的值应该为"ell"
,但是上面的代码执行中却抛出了异常:
Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchMethodException: java.lang.String.substring(java.lang.Integer,java.lang.Integer)
从异常信息不难推断,String
的substring
方法的两个参数都是int
类型,但是当我们把两个基本类型int
通过Object...
传入call
时,int
被包装成了Integer
,与substring
的参数类型不匹配。也就是说,凡是调用含有基本类型参数的函数,call
函数都会失败。
这可怎么办呢?我想到了下面这个看起来十分"暴力"但是有用的解决方案:
public static <T> T call(Object obj, String methodName, Object... params){ try { Method method = obj.getClass().getMethod(methodName, getTypes(params)); return (T) method.invoke(obj, params); } catch (Exception e) { // 遍历obj中的每一个方法 for (Method method : obj.getClass().getMethods()) { try { // 筛选方法名和参数数量相同的方法 if (method.getName().equals(methodName) && method.getParameterCount() == params.length) return (T) method.invoke(obj, params); } catch (Exception ignored) {} } // 找不到方法 throw new RuntimeException(e); }}
简单地说,如果getMethod
找不到匹配的方法,那么就直接遍历对象中所有方法名等于methodName
且参数数量等于params
长度的方法,并依次调用这些方法,如果调用成功就直接返回。
这个实现虽然看起来效率有点低,但是好歹能凑合使用,所以我使用了很长一段时间,直到遇到下面这个需求:
提前获取方法调用的返回值类型,而不实际调用这个方法。
例如,我想要知道将参数1
和4
(两个int
类型的实参)传入String
的substring
方法后,方法返回值的类型:
Class<?> returnType = ReflectUtils.getReturnType(String.class, "substring", 1, 2);
上面代码的预期输出是String.class
。
可以想象,这个getReturnType
方法的签名一定是下面这样的:
public static Class<?> getReturnType(Class<?> type, String methodName, Object... params);
一个很容易想到的实现:
public static Class<?> getReturnType(Class<?> type, String methodName, Object... params){ try { Method method = type.getMethod(methodName, getTypes(params)); return method.getReturnType(); } catch (Exception e) { throw new RuntimeException(e); }}
事实上,这个实现是错误的,原因与上面的call
方法类似。因为Java的自动装箱机制,当我们把两个int
通过Object...
传入时,int
会被包装成Integer
。getReturnType
内部使用getMethod
来查找方法,它实际执行的是下面这条语句:
type.getMethod("substring", Integer.class, Integer.class);
而不是我们期望的:
type.getMethod("substring", int.class, int.class);
当然什么也找不到了。万恶的自动装箱!
而且,在这种情况下,也不可能像call
一样依次尝试调用type
中的所有方法,因为我们仅仅只是想获取方法的返回值,而不希望真正调用这个方法。
下面记录一下我的解决方案。
首先在一个Map
中存储基本类型与包装类型的对应关系:
private static final Map<Class<?>, Class<?>> primitiveAndWrap = new HashMap<>();static{ primitiveAndWrap.put(byte.class, Byte.class); primitiveAndWrap.put(short.class, Short.class); primitiveAndWrap.put(int.class, Integer.class); primitiveAndWrap.put(long.class, Long.class); primitiveAndWrap.put(float.class, Float.class); primitiveAndWrap.put(double.class, Double.class); primitiveAndWrap.put(char.class, Character.class); primitiveAndWrap.put(boolean.class, Boolean.class);}
isPrimitive
方法用于判断一个类型是不是基本类型:
public static boolean isPrimitive(Class<?> type){ return primitiveAndWrap.containsKey(type);}
getWrap
方法用于将基本类型转换为对应的包装类型:
public static Class<?> getWrap(Class<?> type){ if (!isPrimitive(type)) return type; return primitiveAndWrap.get(type);}
match
方法用于判断actualType
是否能被赋值给declaredType
。注意,在进行isAssignableFrom
判断前,使用getWrap
抹平了基本类型与包装类型之间的差距:
private static boolean match(Class<?> declaredType, Class<?> actualType){ return getWrap(declaredType).isAssignableFrom(getWrap(actualType));}
进一步实现一个判断类型数组的match
方法:
private static boolean match(Class<?>[]c1, Class<?>[] c2){ if (c1.length == c2.length) { for (int i = 0; i < c1.length; ++i) { if (!match(c1[i], c2[i])) return false; } return true; } return false;}
接着实现一个getMethod
方法:
private static Method getMethod(Class<?> type, String name, Class<?>[] parameterTypes){ try { return type.getMethod(name, parameterTypes); } catch (Exception e) { for (Method method : type.getMethods()) { if (method.getName().equals(name) && method.getParameterCount() == parameterTypes.length) if (match(method.getParameterTypes(), parameterTypes)) return method; } throw new RuntimeException(e); }}
这个方法十分关键,它用来从某个类型中获取满足条件的方法。在getMethod
内部,首先尝试用Class
的getMethod
来获取方法。如果获取不到,则遍历type
中所有具有指定方法名和指定参数个数的方法,并判断该方法的参数类型是否与parameterTypes
匹配(使用上面的match
方法),即实参类型能否赋值给形参类型。
有了上面这些方法,就可以来实现getReturnType
了:
public static Class<?> getReturnType(Class<?> type, String methodName, Object... params){ return getMethod(type, methodName, getTypes(params)).getReturnType();}
call
的实现也可改写如下:
public static <T> T call(Object obj, String methodName, Object... params){ try { return (T) getMethod(obj.getClass(), methodName, getTypes(params)).invoke(obj, params); } catch (Exception e) { throw new RuntimeException(e); }}
ReflectUtils
的完整代码:https://github.com/byx2000/ReflectUtils
原文转载:http://www.shaoqun.com/a/586579.html
myshow:https://www.ikjzd.com/w/2235
shirley:https://www.ikjzd.com/w/1684
最近在写一个项目,里面需要频繁使用反射操作。由于Java的反射API使用起来比较复杂,所以我决定把常用的反射操作封装成一个工具类:ReflectUtils。在ReflectUtils中,有这么一个call方法:publicstatic<T>Tcall(Objectobj,StringmethodName,Object...params);这个方法利用反射调用某个实例对象的某个方法,ob
杨帆:https://www.ikjzd.com/w/1648
汇通天下:https://www.ikjzd.com/w/2055
亚马逊应用商店:https://www.ikjzd.com/w/531
阿里速卖通在俄推首个网上购车服务:https://www.ikjzd.com/home/18144
淡季如何做好速卖通数据化选品,引爆流量?:https://www.ikjzd.com/home/113544
作为一个有上进心的卖家,这个单刷还是不刷?:https://www.ikjzd.com/home/100879
No comments:
Post a Comment