關於自動裝箱與拆箱在反射調用中引起的注意

1:首先先說一下什麼是自動裝箱與拆箱?

太基本的概念就不說了,直接百度就可以,直接丟上來一個例子就可以知道了。

int one = 1;
// compile error 編譯錯誤就可以確定變量one不是Integer類型,因此Integer類型由getClass方法
//System.out.println(one.getClass());
Object oneObject = one;
//輸出:class java.lang.Integer
System.out.println(oneObject.getClass());


2:反射中獲取方法時候支持自動裝箱或者拆箱嗎?先說結論:不支持。

public class User {
    private int age;
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}

反射獲取setAge方法:


異常信息:


可以看出反射獲取方法時不支持泛型的。具體是什麼原因呢?可以跟中JDK源碼,jdk源碼位置: java.lang.Class#searchMethods ,感興趣可以自已一步一步的跟蹤。


直接看一下java.lang.Class#searchMethods源碼:

private static Method searchMethods(Method[] methods,
                                     String name,
                                     Class<?>[] parameterTypes)
 {
     Method res = null;
     String internedName = name.intern();
     for (int i = 0; i < methods.length; i++) {
         Method m = methods[i];
         //arrayContentsEq(parameterTypes, m.getParameterTypes()) 重點代碼:
         // 此時parameterTypes是一個Class數組,裏面只有一個Integer值(是我們反射時候指定的), m.getParameterTypes()是真是參數類型由於我們定義的age是int,所以m.getParameterTypes()是隻有一個int的class數組
         // 由於兩個數組類型內容不相同所以不等
         if (m.getName() == internedName&& arrayContentsEq(parameterTypes, m.getParameterTypes())
             && (res == null
                 || res.getReturnType().isAssignableFrom(m.getReturnType())))
             res = m;
     }
 
     return (res == null ? res : getReflectionFactory().copyMethod(res));
 }

具體原因參考源碼註釋即可明白。反射獲取方法不支持自動裝箱或拆箱,那反射調用方法是否支持自動裝箱或拆箱呢?答案:支持。

class Main {
    public static void main(String[] args) throws Exception {
        User u = new User();
        u.setAge(10);
        Method intMethod = User.class.getMethod("setAge", int.class);
        int one =1;
        intMethod.invoke(u,one);
        System.out.println(u.getAge());
        intMethod.invoke(u,new Integer(2));
        System.out.println(u.getAge());
 
    }
}

可以成功輸出。


 

 3:由於獲取方法不支持自動裝箱或拆箱,那麼我們在使用反射時候很容易錯誤,如何避免這個問題呢?答案:智者借力而行,當然是用現有的輪子了。那就是apache common beanutils.


可以使用BeanUtils中MethodUtils的invokeMethod方法,這個方法內部在org.apache.commons.beanutils.MethodUtils.getMatchingAccessibleMethod()中做了類型兼容判斷(就是兼容int與Integer等裝拆箱問題)。

 

MethodUtils中還有invokeExactMethod,這個方法時精準調用的意思,沒有做兼容,在User中的setAge是無法通過該方法進行反射調用的,原因如下:

invokeExactMethod方法定義如下:


public static Object invokeExactMethod( final Object object,final String methodName, final Object arg)
 由於User的age是int類型,但是當我們給arg傳入int時候會自動轉爲Integer類型(上面問題1的情況),因此就會出現上面問題2中的問題。但是如果我們的age是Integer類型的話則兩者就可以進行調用了。

 org.apache.commons.beanutils.MethodUtils#getMatchingAccessibleMethod中的兼容處理重點代碼:

public static Method getMatchingAccessibleMethod(
                                            final Class<?> clazz,
                                            final String methodName,
                                            final Class<?>[] parameterTypes) {
    // trace logging
    final Log log = LogFactory.getLog(MethodUtils.class);
    if (log.isTraceEnabled()) {
        log.trace("Matching name=" + methodName + " on " + clazz);
    }
    final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
 
    // see if we can find the method directly
    // most of the time this works and it's much faster
    try {
        // Check the cache first
        Method method = getCachedMethod(md);
        if (method != null) {
            return method;
        }
 
        method = clazz.getMethod(methodName, parameterTypes);
        if (log.isTraceEnabled()) {
            log.trace("Found straight match: " + method);
            log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
        }
 
        setMethodAccessible(method); // Default access superclass workaround
 
        cacheMethod(md, method);
        return method;
 
    } catch (final NoSuchMethodException e) { /* SWALLOW */ }
 
    // search through all methods
    final int paramSize = parameterTypes.length;
    Method bestMatch = null;
    final Method[] methods = clazz.getMethods();
    float bestMatchCost = Float.MAX_VALUE;
    float myCost = Float.MAX_VALUE;
    for (Method method2 : methods) {  //兼容處理開始
        if (method2.getName().equals(methodName)) {
            // log some trace information
            if (log.isTraceEnabled()) {
                log.trace("Found matching name:");
                log.trace(method2);
            }
 
            // compare parameters
            final Class<?>[] methodsParams = method2.getParameterTypes();
            final int methodParamSize = methodsParams.length;
            if (methodParamSize == paramSize) { // 參數個數相同
                boolean match = true;
                for (int n = 0 ; n < methodParamSize; n++) {
                    if (log.isTraceEnabled()) {
                        log.trace("Param=" + parameterTypes[n].getName());
                        log.trace("Method=" + methodsParams[n].getName());
                    }
                    if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) { //重點:判斷參數是否兼容
                        if (log.isTraceEnabled()) {
                            log.trace(methodsParams[n] + " is not assignable from "
                                        + parameterTypes[n]);
                        }
                        match = false;
                        break;
                    }
                }
 
                if (match) {
                    // get accessible version of method
                    final Method method = getAccessibleMethod(clazz, method2);
                    if (method != null) {
                        if (log.isTraceEnabled()) {
                            log.trace(method + " accessible version of "
                                        + method2);
                        }
                        setMethodAccessible(method); // Default access superclass workaround
                        myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
                        if ( myCost < bestMatchCost ) {
                           bestMatch = method;
                           bestMatchCost = myCost;
                        }
                    }
 
                    log.trace("Couldn't find accessible method.");
                }
            }
        }
    }
    if ( bestMatch != null ){
             cacheMethod(md, bestMatch);
    } else {
    // didn't find a match
           log.trace("No match found.");
    }
 
    return bestMatch;
}

org.apache.commons.beanutils.MethodUtils#isAssignmentCompatible 兼容判斷邏輯:

public static final boolean isAssignmentCompatible(final Class<?> parameterType, final Class<?> parameterization) {
    // try plain assignment
    if (parameterType.isAssignableFrom(parameterization)) {
        return true;
    }
 
    if (parameterType.isPrimitive()) {// isPrimitive是jdk提供的方法
        // this method does *not* do widening - you must specify exactly
        // is this the right behaviour?
        final Class<?> parameterWrapperClazz = getPrimitiveWrapper(parameterType); // 工具類庫自己實現的
        if (parameterWrapperClazz != null) {
            return parameterWrapperClazz.equals(parameterization);
        }
    }
 
    return false;
}

 org.apache.commons.beanutils.MethodUtils#getPrimitiveWrapper 源碼(很簡單,沒有想象的複雜):

public static Class<?> getPrimitiveWrapper(final Class<?> primitiveType) {
        // does anyone know a better strategy than comparing names?
        if (boolean.class.equals(primitiveType)) {
            return Boolean.class;
        } else if (float.class.equals(primitiveType)) {
            return Float.class;
        } else if (long.class.equals(primitiveType)) {
            return Long.class;
        } else if (int.class.equals(primitiveType)) {
            return Integer.class;
        } else if (short.class.equals(primitiveType)) {
            return Short.class;
        } else if (byte.class.equals(primitiveType)) {
            return Byte.class;
        } else if (double.class.equals(primitiveType)) {
            return Double.class;
        } else if (char.class.equals(primitiveType)) {
            return Character.class;
        } else {
 
            return null;
        }
    }
就是一堆if else 判斷。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章