Java技術之反射 | wingjay

關於Java反射機制的文章很多,這次換種方式來講解反射的作用。
本文涉及到的知識點:class.getDeclaredXXX()XXX.getModifiers()method.getReturnType()method.getParameterTypes()method.isAnnotationPresent(XXX.class)Modifier.isStatic(method.getModifiers())constructor.newInstance(XX)
本文涉及代碼:https://github.com/wingjay/HelloJava/blob/master/data-structure/src/reflection/ForArticle.java

先來看一個熟悉的 Class

首先,簡單來說,反射就是在運行時可以獲取任意 ClassObject 內部所有成員屬性,如成員變量、成員方法、構造函數和 Annotation。

這次先給出一個大家非常熟悉的 ClassUserBean

本文要完成的任務就是,在只有一個 UserBean.getClass() 的情況下,利用代碼打印出其內部所有成員變量、方法,並動態執行內部用 @Invoke 修飾的成員方法

package com.wingjay.reflection;

public class UserBean {

    public String userName;

    private long userId;

    public UserBean(String userName, long userId) {
        this.userName = userName;
        this.userId = userId;
    }

    public String getName() {
        return userName;
    }

    public long getId() {
        return userId;
    }

    @Invoke
    public static void staticMethod(String devName) {
        System.out.printf("Hi %s, I'm a static method", devName);
    }

    @Invoke
    public void publicMethod() {
        System.out.println("I'm a public method");
    }

    @Invoke
    private void privateMethod() {
        System.out.println("I'm a private method");
    }
}

在只提供一個 UserBean 的 Class 情況下,

  1. 打印出這個 Class 內部的所有成員變量、成員方法、構造函數,包括 private 的;
  2. 調用這個 Class 內部的三個用 @Invoke 修飾的方法:staticMethod(), publicMethod(), privateMethod()

1. 打印 UserBean Class 裏的所有成員變量、成員方法,包括 private 的

首先我們擁有一個 Class userBeanClass = UserBean.class,我們要利用這個 Class 來打印它的成員變量 userNameuserId

打印成員變量

那麼如何獲取成員變量呢,我們發現,Java 裏提供了 Field 這個類來表示成員變量,提供了 clazz.getDeclaredFields() 來獲取一個類內部聲明的所有變量。因此,可以利用下面的代碼獲取 userBeanClass 內部所有的成員變量。

Field[] fields = userBeanClass.getDeclaredFields();

那麼,我們如何將一個 field 對象打印成 private String userName; 這種形式呢?或者說如何分別找到 privateStringuserName 這三個值呢?

其實,Field 裏包含了三種元素來對應它們,分別是ModifierTypeName

private <-- field.getModifiers();
String <-- field.getType();
userName <-- field.getName();
// fields
Field[] fields = userBeanClass.getDeclaredFields();

for(Field field : fields) {
    String fieldString = "";
    fieldString += Modifier.toString(field.getModifiers()) + " "; // `private`
    fieldString += field.getType().getSimpleName() + " "; // `String`
    fieldString += field.getName(); // `userName`
    fieldString += ";";
    System.out.println(fieldString);
}

打印結果:

public String userName;
private long userId;

打印成員方法

類似成員變量的 Field,成員方法也有對應的類 Method,首先可以通過 Method[] methods = userBeanClass.getDeclaredMethods(); 獲得所有的成員方法,然後,爲了打印形如:public static void staticMethod(String devName)的數據,可以利用下列 method 提供的方法:

private static <-- method.getModifiers();
void <-- method.getReturnType();
staticMethod <-- method.getName();
String <-- method.getParameterTypes();

因此可以得到:

Method[] methods = userBeanClass.getDeclaredMethods();
for (Method method : methods) {
    String methodString = Modifier.toString(method.getModifiers()) + " " ; // private static
    methodString += method.getReturnType().getSimpleName() + " "; // void
    methodString += method.getName() + "("; // staticMethod 
    Class[] parameters = method.getParameterTypes();
    for (Class parameter : parameters) {
        methodString += parameter.getSimpleName() + " "; // String
    }
    methodString += ")";
    System.out.println(methodString);
}

打印結果如下:

public String getName()
public long getId()
public static void staticMethod(String )
public void publicMethod()
private void privateMethod()

可以完整的打印所有成員方法,無論是 public 還是 private,而且能打印 static 關鍵字。

打印構造函數

其實構造函數和成員函數非常類似,Java 裏提供了 Constructor 來表示構造函數,爲了打印 public UserBean(String userName, long userId),可以利用下面的函數實現:

// constructors
Constructor[] constructors = userBeanClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
    String s = Modifier.toString(constructor.getModifiers()) + " ";
    s += constructor.getName() + "(";
    Class[] parameters = constructor.getParameterTypes();
    for (Class parameter : parameters) {
        s += parameter.getSimpleName() + ", ";
    }
    s += ")";
    System.out.println(s);
}

打印結果如下:

public com.wingjay.reflection.UserBean(String, long)

2. 調用 Class 內部的用 @Invoke 修飾的方法

從上面知道,我們可以利用 class.getDeclaredMethods() 獲取一個類內部所有成員方法,接下來我們還要做的事是:

  1. 判斷這個方法是否被 @Invoke 修飾
  2. 如果修飾,判斷這個方法是不是 static
  3. 如果是 static,則可以直接用 class 調用
  4. 如果不是 static,那就需要實例化一個對象來調用
  5. 如果這個方法是 private 的,要記得 setAccessible(true)

如果分別實現上面的一些功能呢?

  • 爲了判斷一個 Method 是否被某個 Annotation 修飾,可以用 method.isAnnotationPresent(Invoke.class)
  • 關於 static private,我們可以用 Modifier 類提供的 Modifier.isStatic()Modifier.isPrivate()來判斷;
  • 如果 Method 不是 static,那就要實例化對象,我們可以用 class.newInstance() 或者 constructor.newInstance(params) 來得到實例。
  • 關於執行一個 Method,可以使用 method.invoke(object, ...params) 方法,如果方法不是 static 就必須實例化一個 object 傳給它,否則可以傳 null

實現如下:

Method[] methods = userBeanClass.getDeclaredMethods(); // 獲取所有成員方法
for (Method method : methods) {
    if (method.isAnnotationPresent(Invoke.class)) { // 判斷是否被 @Invoke 修飾
        if (Modifier.isStatic(method.getModifiers())) { // 如果是 static 方法
            method.invoke(null, "wingjay"); // 直接調用,並傳入需要的參數 devName
        } else {
            Class[] params = {String.class, long.class};
            Constructor constructor = userBeanClass.getDeclaredConstructor(params); // 獲取參數格式爲 String,long 的構造函數
            Object userBean = constructor.newInstance("wingjay", 11); // 利用構造函數進行實例化,得到 Object
            if (Modifier.isPrivate(method.getModifiers())) {
                method.setAccessible(true); // 如果是 private 的方法,需要獲取其調用權限
            }
            method.invoke(userBean); // 調用 method,無須參數
        }
    }
}

打印結果:

I'm a public method
Hi wingjay, I'm a static method
I'm a private method

可見三個方法都正常調用了,而且 public static void staticMethod(String devName) 的參數 devName 也正常傳進去了。

小結

基於上面兩個實踐,我們已經能夠利用反射機制,在運行狀態下把一個 Class 的內部成員方法、成員變量和構造函數全部獲取到,並且能夠進行實例化、直接調用內部的成員方法。

因此,有了反射機制,我們即使只有動態得到的 Class,也能直接得到它內部的信息、甚至調用它內部的方法。

瞭解了反射後,下文我將介紹 Annotation 一些有趣的自定義實現,合理地利用 Annotation 能讓代碼更簡潔,自動生成代碼,實現一些常規難以實現的功能。

281665-9ffa921d5b9d214a.jpg
Android技術·面試技巧·職業感悟
發佈了45 篇原創文章 · 獲贊 12 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章