banner
jzman

jzman

Coding、思考、自觉。
github

Java系列之反射

最近知識比較零碎,不適合整理成文,來一篇以前的關於反射的學習筆記,主要內容如下

  1. 反射機制
  2. 反射獲取類的信息
  3. 反射操作類的信息
  4. 反射獲取泛型
  5. 反射獲取註解信息

反射機制#

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 在一定程度上可以提高反射的運行速度。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。