Java反射的使用姿式一覽

反射的學習使用

平常的學習工做中,可能用到反射的地方不太多,但看看一些優秀框架的源碼,會發現基本上都離不開反射的使用;所以本篇博文將專一下如何使用java

本片博文佈局以下:框架

  1. 反射是什麼,有什麼用,能夠作什麼ide

  2. 如何使用反射工具

  3. 實例:佈局

    • 利用反射方式,獲取一個類的全部成員變量的name及值
    • 經過反射方式,修改對象的私有成員變量
    • 會經過寫一個BeanUtils實現對象的成員變量值拷貝來覆蓋上面兩個場景

I. 反射定義

指程序能夠訪問、檢測和修改它自己狀態或行爲的一種能力性能

直接說定義的話,可能並不能很是清晰的解釋說明,結合做用進行描述學習

反射能夠幹什麼?測試

在運行時構造任意一個類的對象。
在運行時判斷任意一個對象所屬的類。
在運行時判斷任意一個類所具備的成員變量和方法。
在運行時調用任意一個對象的方法

有了上面四點,基本上你想幹嗎就能夠幹嗎,好比我如今就有下面這個類this

public class RefectTest extends MyRefect implements IRefect {

    private static String s1 = "hello";

    private static int s2 = 100;

    private int s3 = 200;

    private boolean ans;

    protected RefectTest next;

    public RefectTest() {
    }

    public RefectTest(int s3, boolean ans, RefectTest next) {
        this.s3 = s3;
        this.ans = ans;
        this.next = next;
    }
    
    public RefectTest next() {
      return next;
    }
    
    private int count(int a, int b) {
      return a + b;
    }
}

如今我有了clz,其賦值語句爲 Class clz = RefectTest.class, 那麼我能夠幹啥?hibernate

  1. 建立一個 RefectTest 對象

    // 如有默認構造方法
    RefectTest instance = clz.newIntance();
    
    // 若須要傳參數
    Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class);
    RefectTest instance2 =  con.newInstance(10, true, new RefectTest());
  2. 判斷父類是不是 MyRefect

    // 判斷MyRefect是否爲clz的父類
    boolean ans = MyRefect.class.isAssignableFrom(clz);
  3. 獲取全部的成員變量

    // 獲取全部的成員變量(包括私有的)
    Field[] fields = clz.getDeclaredFields();
  4. 獲取全部的方法

    // 獲取全部的成員方法(包括私有方法)
    Method[] methods = clz.getDeclaredMethods();

上面給出了能夠幹些什麼,並給了對應的簡單示例,引入了幾個新的類Constructor, Field, Method, 下面將詳細解釋這三個類是什麼,怎麼用

II. 反射的使用

努力結合實際的應用場景,給出每種利用反射的實現對應需求的使用姿式,有些場景可能並非特別貼切,歡迎提出給合適的場景以此進行替換

1. 經過反射建立對象

這是個比較常見的場景,我在使用了自定義註解時,一般會這麼晚

應用場景:

我定義了一個校驗器的註解ValDot,註解中有個校驗規則class對象,以下

public interface ICheckRule {
    boolean check(Object ... obj);
}

public class DefaultCheckRule implements ICheckRule {
    @Override
    public boolean check(Object... obj) {
        return false;
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckDot {
    // 校驗規則
    Class<? extends ICheckRule> check() default DefaultCheckRule.class;
}

上面定義了註解和校驗條件,接着進入總體,在切面中,須要獲取

@Aspect
@Component
public class CheckAspect {
    @Before("@annotation(checkDot)")
    public void process(JoinPoint joinPoint, CheckDot checkDot)
      throws IllegalAccessException, InstantiationException {
        // 注意,這裏獲取註解上的校驗規則類,並獲取實例
        ICheckRule rule = checkDot.check().newInstance();
        
        if(rule.check(joinPoint.getArgs())) {
            throw new IllegalStateException("check argument error!");
        }
    }
}

上面是一個較好的利用反射獲取實例的應用場景,想想,若是不用反射,這個校驗規則怎麼傳進來呢,這個時候就沒那麼方便了(固然也不是不能夠,最簡單的就是拿一個Holder持有類名到類對象的映射關係,而後在註解中傳類名,也能夠達到上面的效果)

還有一種場景可能就比較蛋疼了,若是一個類沒有默認構造方法,經過反射就無法直接用class.newInstanace()


Constructor構造器類

根據Class優先獲取到 Constructor 對象,而後傳入須要的構造參數, 測試以下

public class ConTest {

    private int a,b;

    public ConTest(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public String toString() {
        return "ConTest{" + "a=" + a + ", b=" + b + '}';
    }

    public static void main(String[] args) throws Exception {
        Class clz = ConTest.class;
        // 獲取對應的構造器(注意參數類型)
        Constructor constructor = clz.getConstructor(int.class, int.class);
        // 建立實例(注意參數要匹配)
        ConTest test = (ConTest) constructor.newInstance(10, 20);
        System.out.println(test.toString());
    }
}

輸出

ConTest{a=10, b=20}

通常經常使用下面四種方式獲取

// 根據參數類型獲取匹配的構造器
Constructor getConstructor(Class[] params)
 
// 獲取全部的
Constructor[] getConstructors() 

// 相比較前面的,這裏能夠獲取私有方法 
Constructor getDeclaredConstructor(Class[] params)
 
// 能夠獲取私有方法
Constructor[] getDeclaredConstructors()

2. 判斷class的繼承關係

判斷是否爲基礎數據類型

基本類型較爲特殊,因此JDK很人性化的給封裝了一個方法,Class#isPrimitive

所以返回true的類型有:

  • int
  • long
  • short
  • byte
  • char
  • boolean

封裝後的類型,返回的依然是false

<font color="red">附帶一句,是沒有null.class這種用法的</font>


判斷是否爲另外一個類的子類,另外一個接口的實現類

一般咱們利用 instanceof 關鍵字來判斷繼承關係,可是這個是針對對象來的,如今給一個class,要怎麼玩?

看下面,主要就是 Class#isAssignableFrom() 的功勞了

public class ExtendTest {

    interface ITest {}
    
    abstract class ATest {
        abstract public void print();
    }

    class TestClz extends ATest implements ITest {
        @Override
        public void print() {
            System.out.println("TestClz");
        }
    }


    public static void main(String[] args) {
        Class clz = TestClz.class;
        
        System.out.println(ATest.class.isAssignableFrom(clz));
        System.out.println(ITest.class.isAssignableFrom(clz));
    }
}

須要注意一點,父類做爲調用方,子類做爲參數

結合泛型時,獲取泛型的實際類型

泛型,又是一個有意思的功能,這裏很少說,繼承一個泛型基類,而後問題是如何經過反射得到泛型簽名中的類型,通常會在繼承或實現泛型接口時會用到它。

class A<T, ID> {
}

class B extends A<String, Integer> {
}

public static void main(String[] args) {
    System.out.println(B.class.getGenericSuperclass());
}

換成泛型接口呢 ?

interface A<T, ID> {  
}  
  
class B implements A<String, Integer> {  
}

public static void main(String[] args) {
    ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces()[0];  
    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
    for (Type actualTypeArgument : actualTypeArguments) {  
        System.out.println(actualTypeArgument);  
    }
}

3. 獲取成員變量

獲取成員變量,主要是根據 B.class.getDeclaredFields() 來獲取全部聲明的變量,這個應用場景會和下面的獲取方法並執行聯合一塊兒說明

// 獲取指定的公共成員變量
Field getField(String name)
 
// 得到全部公共字段 
Field[] getFields()

// 獲取指定聲明的成員變量(包括prive)
Field getDeclaredField(String name)

// 獲取全部聲明的成員變量
Field[] getDeclaredFields()

這個主要返回 Field對象,如今有了Field,能夠作些啥?

  1. 判斷成員的修飾 Field#getModifiers()

    int modify = field.getModifiers();
    // 是不是靜態變量
    boolean ans = Modifier.isStatic(modifier);
    // 是不是公共變量
    boolean ans = Modifier.isPublic(modifier);
    // 是否不可變
    boolean ans = Modifier.isFinal(modifier);
    // ...
  2. 獲取成員的變量名 : field#getName()

  3. 獲取成員對應的value: field#get(instance)

    • 對於靜態成員,instance能夠爲null
    • 對於非靜態成員,instance必須爲一個實例對象
  4. 獲取註解: field#getAnnotations()

    • 這個就厲害了,hibernate的校驗框架,在成員變量上加一個註解Max,就能夠設置參數的最大值,其實就是經過反射獲取到註解,而後進行相應的邏輯

4. 獲取方法

獲取方法,同上面的差很少,也有四種方式

// 根據方法名,參數類型獲取公共方法
Method getMethod(String name, Class[] params)

// 獲取全部的公共方法
Method[] getMethods()

// 根據方法名,參數類型,獲取聲明的方法(包括私有)
Method getDeclaredMethod(String name, Class[] params)

// 獲取全部聲明的方法
Method[] getDeclaredMethods()

返回了一個Method類,那麼這個東西又有一些什麼功能?

  1. 獲取方法名 Method#getName()

  2. 獲取方法所在的類 : Method#getDeclaringClass()

  3. 獲取方法返回類型 : Method#getReturnType()

  4. 獲取方法上的註解 : Method#getAnnotations()

  5. 執行方法 有了這個就能夠作不少事情了,實例中給出說明

    // 設置方法可訪問(即私有方法也能夠被調用)
    method.setAccessible(true);
    // instance爲實例對象, args爲傳入參數
    method.invoke(instance, args)

III. 實例DEMO

經過反射的方式,實現一個 BeanUtils,實現Bean的拷貝

當一個Bean有較多的成員變量時,若是咱們採用最原始的setXXX()來一次賦值的時候,一是實現比較繁瑣,其次就是當Bean的字段發生變更以後,也須要同步的修改,那麼咱們藉助反射的方式,實現一個優雅的 BeanUtils 工具類

public class BeanUtils {
    public static void copy(Object source, Object dest) throws Exception {
        Class destClz = dest.getClass();

        // 獲取目標的全部成員
        Field[] destFields = destClz.getDeclaredFields();
        Object value;
        for (Field field : destFields) { // 遍歷全部的成員,並賦值
            // 獲取value值
            value = getVal(field.getName(), source);

            field.setAccessible(true);
            field.set(dest, value);
        }
    }


    private static Object getVal(String name, Object obj) throws Exception {
        try {
            // 優先獲取obj中同名的成員變量
            Field field = obj.getClass().getField(name);
            field.setAccessible(true);
            return field.get(obj);
        } catch (NoSuchFieldException e) {
            // 表示沒有同名的變量 
        }

        // 獲取對應的 getXxx() 或者 isXxx() 方法
        name = name.substring(0, 1).toUpperCase() + name.substring(1);
        String methodName = "get" + name;
        String methodName2 = "is" + name;
        Method[] methods = obj.getClass().getMethods();
        for (Method method : methods) {
            // 只獲取無參的方法
            if (method.getParameterCount() > 0) {
                continue;
            }

            if (method.getName().equals(methodName)
                    || method.getName().equals(methodName2)) {
                return method.invoke(obj);
            }
        }
        
        // 沒有匹配到,這裏返回null其實是不合適的
        // 由於若是原屬性爲基本數據類型,賦值null爲報錯
        throw new Exception();
    }
}

IV. 小結

反射的四種用途

  1. 建立一個 RefectTest 對象

    // 如有默認構造方法
    RefectTest instance = clz.newIntance();
    
    // 若須要傳參數
    Constructor con = clz.getConstructor(int.class, boolean.class, RefectTest.class);
    RefectTest instance2 =  con.newInstance(10, true, new RefectTest());
  2. 判斷父類是不是 MyRefect

    // 判斷MyRefect是否爲clz的父類
    boolean ans = MyRefect.class.isAssignableFrom(clz);
  3. 獲取全部的成員變量

    // 獲取全部的成員變量(包括私有的)
    Field[] fields = clz.getDeclaredFields();
  4. 獲取全部的方法

    // 獲取全部的成員方法(包括私有方法)
    Method[] methods = clz.getDeclaredMethods();

使用注意事項

  1. 操做私有變量,私有方法時,先設置field.setAccessible(true);確保可訪問
  2. 反射會帶來額外的性能開銷
  3. 能夠用 Class#isAssignableFrom() 來判斷類繼承關係
  4. 能夠用 Class#isPrimitive()判斷是否爲基本數據類型
  5. 能夠用 Class#getGenericSuperclass() 獲取泛型類型

V. 其餘

聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,看法不全,若有問題,歡迎批評指正

掃描關注,java分享

QrCode

相關文章
相關標籤/搜索