最近知識比較零碎,不適合整理成文,來一篇以前的關於反射的學習筆記,主要內容如下
- 反射機制
- 反射獲取類的信息
- 反射操作類的信息
- 反射獲取泛型
- 反射獲取註解信息
反射機制#
Java 的反射機制是指在運行狀態下,對於任意一個類,都能夠知道這個類的所有屬性和方法,反射是一種可在代碼運行時動態獲取類的信息的一種機制,可通過反射獲取在編譯期不可能獲取到的類的信息,當一個任意類被類加載器 (ClassLoader) 首次加載之後會自動生成一個該類對應的 Class 對象,這個 Class 對象保存了對應類的所有信息。這種當一個任意類被類加載器加載之後,動態獲取 Class 對象的信息以及動態操作 Class 對象的屬性和方法的功能稱之為 Java 的反射機制。
Java 反射機制的關鍵是獲取某個任意類的 Class 對象,下面的內容就是如何通過這個 Class 對象獲取和動態操作類的相關信息,為了便於後文中對反射的使用,這裡先創建一個 User 類供下文中使用,具體如下:
package com.manu.reflection.bean;
/**
* 反射測試類
*/
public class User {
private int id;
private String name;
private String password;
public User() {
super();
}
public User(int id, String name, String password) {
super();
this.id = id;
this.name = name;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", password=" + password + "]";
}
}
反射獲取類的信息#
這裡總結一下如何獲取類的基本信息,如類的構造方法、屬性、方法等,可通過某個類對應的 Class 對象對應的 getter 方法獲取某個類的名稱、構造方法、屬性、方法等,下面以獲取某個類的構造方法為例說明獲取方式不同:
- clazz.getConstructors (): 表示獲取某個類中修飾符為 public 的所有構造方法,如 getFields ()、getMethods () 還包括從父類繼承來的 public 修飾的屬性和方法。
- clazz.getDeclaredConstructors (): 表示獲取某個類中所有聲明的構造方法,只限定於本類中。
此外,還可獲取指定的構造方法、屬性、方法等,下面的代碼主要以獲取已定義 (Declared) 的構造方法、屬性、方法為例,參考如下:
/**
* 反射獲取類的信息
*/
private static void getReflectClassInfo() {
try {
//獲取某個類的Class對象
String name = "com.manu.reflection.bean.User";
Class clazz = Class.forName(name);
//反射獲取類的名稱
System.out.println("----------反射獲取類的名稱----------");
String n1 = clazz.getName();//完整路徑:包名+類名 (com.manu.reflection.bean.User)
String n2 = clazz.getSimpleName();//類名(User)
System.out.println("獲取類的名稱n1:"+n1);
System.out.println("獲取類的名稱n2:"+n2);
//反射獲取類的構造方法
System.out.println("----------反射獲取類的構造方法----------");
Constructor<User> c1 = clazz.getDeclaredConstructor(null);
System.out.println("獲取無參構造方法:"+c1);
Constructor<User> c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class);
System.out.println("獲取有參構造方法:"+c2);
Constructor[] constructors = clazz.getDeclaredConstructors();
for(Constructor c: constructors) {
System.out.println("獲取所有的構造方法:"+c);
}
//反射獲取類的屬性
System.out.println("----------反射獲取類的屬性----------");
Field f1 = clazz.getDeclaredField("name");
System.out.println("獲取名稱為name的屬性:"+f1);
Field[] fields = clazz.getDeclaredFields();
for(Field f : fields) {
System.out.println("獲取所有的屬性:"+f);
}
//反射獲取類的方法
System.out.println("----------反射獲取類的方法----------");
Method m1 = clazz.getDeclaredMethod("getName", null);//獲取無參方法
Method m2 = clazz.getDeclaredMethod("setName", String.class);//獲取有參方法
System.out.println("獲取方法名為getName的方法m1:"+m1);
System.out.println("獲取方法名為setName的方法m2:"+m2);
Method[] mathods = clazz.getDeclaredMethods();
for(Method m: mathods) {
System.out.println("獲取所有方法:"+m);
}
} catch (Exception e) {
e.printStackTrace();
}
}
通過反射獲取某個類的相關信息主要如上,來上述代碼的執行結果如下:
----------反射獲取類的名稱----------
獲取類的名稱n1:com.manu.reflection.bean.User
獲取類的名稱n2:User
----------反射獲取類的構造方法----------
獲取無參構造方法:public com.manu.reflection.bean.User()
獲取有參構造方法:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
獲取所有的構造方法:public com.manu.reflection.bean.User()
獲取所有的構造方法:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
----------反射獲取類的屬性----------
獲取名稱為name的屬性:private java.lang.String com.manu.reflection.bean.User.name
獲取所有的屬性:private int com.manu.reflection.bean.User.id
獲取所有的屬性:private java.lang.String com.manu.reflection.bean.User.name
獲取所有的屬性:private java.lang.String com.manu.reflection.bean.User.password
----------反射獲取類的方法----------
獲取方法名為getName的方法m1:public java.lang.String com.manu.reflection.bean.User.getName()
獲取方法名為setName的方法m2:public void com.manu.reflection.bean.User.setName(java.lang.String)
獲取所有方法:public java.lang.String com.manu.reflection.bean.User.toString()
獲取所有方法:public java.lang.String com.manu.reflection.bean.User.getName()
獲取所有方法:public int com.manu.reflection.bean.User.getId()
獲取所有方法:public void com.manu.reflection.bean.User.setName(java.lang.String)
獲取所有方法:public java.lang.String com.manu.reflection.bean.User.getPassword()
獲取所有方法:public void com.manu.reflection.bean.User.setId(int)
獲取所有方法:public void com.manu.reflection.bean.User.setPassword(java.lang.String)
反射操作類的信息#
通過 Java 的反射機制可以獲取的某個類的構造方法、屬性以及方法,然後就可以對該類進行相關操作了,下面是將通過 Java 的反射機制構建該類的對象、操作該類對象的屬性以及調用該類對象的方法,具體參考如下:
/**
* 反射操作類的信息
*/
private static void setReflectClassInfo() {
try {
//獲取某個類的Class對象
String name = "com.manu.reflection.bean.User";
Class clazz = Class.forName(name);
//反射操作類的構造方法
System.out.println("----------反射操作類的構造方法----------");
Constructor<User> c1 = clazz.getDeclaredConstructor(null);//獲取無參構造方法
Constructor<User> c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class);//獲取帶參構造方法
User u1 = c1.newInstance();
User u2 = c2.newInstance(1000,"jzman-blog","111111");
System.out.println("u1:"+u1);
System.out.println("u2:"+u2);
//反射操作類的屬性
System.out.println("----------反射操作類的屬性----------");
User u3 = c1.newInstance();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);//設置該屬性不需要安全檢查,可直接放訪問
field.set(u3, "jzman");//反射設置User對象的name屬性值
System.out.println("u3:"+u3);
System.out.println("獲取User對象u3的name屬性值:"+field.get(u3));
//反射操作類的方法
System.out.println("----------反射操作類的方法----------");
User u4 = c1.newInstance();
Method method = clazz.getDeclaredMethod("setPassword", String.class);
method.invoke(u4, "222222");//設置User對象u4的password屬性,等同於u4.setPassword("222222);
System.out.println("u4:"+u4);
} catch (Exception e) {
e.printStackTrace();
}
}
上述代碼的執行結果如下:
----------反射操作類的構造方法----------
u1:User [id=0, name=null, password=null]
u2:User [id=1000, name=jzman-blog, password=111111]
----------反射操作類的屬性----------
u3:User [id=0, name=jzman, password=null]
獲取User對象u3的name屬性值:jzman
----------反射操作類的方法----------
u4:User [id=0, name=null, password=222222]
實際開發中肯定會遇到某個組件中需要某個屬性,但是該屬性又是私有的,這時候就可以使用 Java 的反射機制了。
反射操作泛型#
Java 采用泛型擦除的機制,Java 中的泛型僅僅是給編譯器 javac 使用的,這樣可確保數據的安全性的免去強制類型轉換的麻煩,當編譯完成之後,所有和泛型相關的類型將會被全部擦除,為了能夠使用反射操作這些類型,新增了四種類型 GenericArrayType、ParameterizedType、TypeVariable 和 WildcardType 來表示不能被歸一到 Class 類中的類型但又是和原始類型齊名的類型,也就是說正常的能夠獲取對應的 Class 對象的 Type 還是 Class 對象,如基本數據類型。
反射操作泛型就要涉及到 Java 中的 Type, 在 Java 中 Type 表示所有類型的公共接口,這些類型包括原始類型、參數化類型、數組類型、類型變量和基本類型,其聲明如下:
/**
* Type 是 Java 語言中所有類型的公共接口
* 這些類型包括原始類型、參數化類型、數組類型、類型變量和基本類型
*/
public interface Type {
default String getTypeName() {
return toString();
}
}
Type 有四個直接子接口,具體含義如下:
GenericArrayType:表示泛型數組類型,如 ArraryList<String>[] listArrary
ParameterizedType:表示參數化類型(泛型),如 ArrayList<String> list
TypeVariable<T>:表示類型變量,如T
WildcardType:表示一個通配符類型,如 ?、? extends Number 或 ? super Integer
下面以 GenericArrayType 和 ParameterizedType 為例來說明如何使用反射來操作泛型類型,具體如下:
//用來測試獲取泛型的屬性
private String[] array;
private List<String>[] listArray;
//用來測試獲取泛型的方法
private String testGenericType(Map<String,Integer> map, String[] array, int name,User user) {
System.out.println("testGenericType");
return null;
}
//通過反射獲取泛型
private static void refectGenericType() {
try {
System.out.println("---------GenericArrayType---------");
//獲取泛型數組(GenericArrayType)
Field field = ReflectTest02.class.getDeclaredField("listArray");//獲取屬性listArray
GenericArrayType type = (GenericArrayType) field.getGenericType();
System.out.println("獲取泛型數組:"+type);
System.out.println("---------ParameterizedType---------");
//獲取參數化類型(泛型)(ParameterizedType)
Method method = ReflectTest02.class.getDeclaredMethod("testGenericType", Map.class,String[].class,int.class,User.class);
Type[] types = method.getGenericParameterTypes();//獲得方法參數類型
for(Type type1: types) {
System.out.println("方法參數類型:"+type1);
if(type1 instanceof ParameterizedType) {
System.out.println("ParameterizedType:"+type1);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
上述代碼中方法 refectGenericType 的執行結果如下:
---------GenericArrayType---------
獲取泛型數組:java.util.List<java.lang.String>[]
---------ParameterizedType---------
方法參數類型:java.util.Map<java.lang.String, java.lang.Integer>
ParameterizedType:java.util.Map<java.lang.String, java.lang.Integer>
方法參數類型:class [Ljava.lang.String;
方法參數類型:int
方法參數類型:class com.manu.reflection.bean.User
可參照代碼查看對應的輸出結果。
反射獲取註解信息#
通過反射還可以獲取註解信息,記如果對註解比較陌生可以參考之前分享的一篇文章 Java 系列之註解,這裡簡單模仿數據庫表字段與 Java 對象的屬性是如何通過註解信息一一對應的,為什麼我們使用一些數據庫框架的時候,通過一些表註解、字段註解就能夠創建表,這裡就涉及到使用反射來獲取註解信息來生成 SQL, 進而生成對應的表。
創建兩個註解分別作為表註解和字段註解,參考如下:
//表註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation {
String value();
}
//字段註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {
String column();
int length();
String type();
}
然後,創建一個類並使用該註解,參考如下:
/**
* 反射讀取註解信息測試Bean
* @author jzman
*/
@TableAnnotation(value = "student_table")
public class Student {
@FieldAnnotation(column = "uid", length = 20, type = "int")
private int sId;
@FieldAnnotation(column = "name", length = 10, type = "varchar")
private String sName;
@FieldAnnotation(column = "uiaged", length = 3, type = "varchar")
private int sAge;
//setter、getter方法
//...
}
最後,獲取註解信息,參考如下:
/**
* 反射獲取註解信息
* @author jzman
*/
public class ReflectTest03 {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.manu.reflection.bean.Student");
//反射獲取類的註解信息
TableAnnotation tableAnnotation = (TableAnnotation) clazz.getAnnotation(TableAnnotation.class);
System.out.println("反射獲取類的註解信息:"+tableAnnotation);
//反射獲取屬性的註解信息
Field field = clazz.getDeclaredField("sName");
FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println("反射獲取屬性的註解信息:"+fieldAnnotation);
//獲取其他註解信息使用方式類似
//...
} catch (Exception e) {
e.printStackTrace();
}
}
}
上述代碼的執行結果如下:
反射獲取類的註解信息:@com.manu.reflection.TableAnnotation(value=student_table)
反射獲取屬性的註解信息:@com.manu.reflection.FieldAnnotation(column=name, length=10, type=varchar)
顯然,通過反射獲取到了相應的註解信息,這些註解信息標注了該類對應數據庫表的一些關鍵信息,然後就可以生成對應的 SQL 語句,這樣就不難理解數據庫表字段與 Java 對象屬性的映射關係了。
使用反射獲取私有屬性或私有方法是必須設置 setAccessible 為 true 才能跳過 Java 安全檢查,從而獲取私有的屬性、方法等,同時設置 setAccessible 為 true 在一定程度上可以提高反射的運行速度。