關於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
首先,簡單來說,反射就是在運行時可以獲取任意 Class
或 Object
內部所有成員屬性,如成員變量、成員方法、構造函數和 Annotation。
這次先給出一個大家非常熟悉的 Class
:UserBean
。
本文要完成的任務就是,在只有一個 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 情況下,
- 打印出這個
Class
內部的所有成員變量、成員方法、構造函數,包括private
的; - 調用這個
Class
內部的三個用@Invoke
修飾的方法:staticMethod()
,publicMethod()
,privateMethod()
;
1. 打印 UserBean Class 裏的所有成員變量、成員方法,包括 private 的
首先我們擁有一個 Class userBeanClass = UserBean.class
,我們要利用這個 Class
來打印它的成員變量 userName
和 userId
。
打印成員變量
那麼如何獲取成員變量呢,我們發現,Java 裏提供了 Field
這個類來表示成員變量,提供了 clazz.getDeclaredFields()
來獲取一個類內部聲明的所有變量。因此,可以利用下面的代碼獲取 userBeanClass
內部所有的成員變量。
Field[] fields = userBeanClass.getDeclaredFields();
那麼,我們如何將一個 field
對象打印成 private String userName;
這種形式呢?或者說如何分別找到 private
、String
、userName
這三個值呢?
其實,Field
裏包含了三種元素來對應它們,分別是Modifier
、Type
、Name
。
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()
獲取一個類內部所有成員方法,接下來我們還要做的事是:
- 判斷這個方法是否被
@Invoke
修飾 - 如果修飾,判斷這個方法是不是
static
的 - 如果是
static
,則可以直接用 class 調用 - 如果不是
static
,那就需要實例化一個對象來調用 - 如果這個方法是
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
能讓代碼更簡潔,自動生成代碼,實現一些常規難以實現的功能。