反射在Java編程中是很常用的功能,開發和閱讀源碼時,總能看到反射的身影。這裏要強調一下,Java的反射真的很簡單,很簡單,很多人以爲Java的反射是一個很高深的知識點,一直不敢去觸碰,以至於成爲很多程序員的短板。接下來就一起來看看這個簡單了一逼的反射機制
Java的反射
反射概述
Java的反射是 在運行狀態中 ,對於任何一個類,都能知道它的所有屬性和方法;對於任何一個對象,都能調用它的所有屬性和方法。這種能夠 動態獲取類的信息 和 動態調用對象的方法 的特性就是Java的反射機制
簡單的說反射就是:動態獲取類的信息 和 動態調用對象的方法(或屬性)
反射的關鍵:獲取代表字節碼文件(.class文件)的Class對象
注: .java 文件通過編譯最終生成 .class 文件
注: Class對象代表着 .class(字節碼文件) [ 當然要先得到Class對象 ]
類加載簡單示例
先簡單瞭解一下類的加載過程
Java編譯器會將 .java 文件編譯成 .class 文件
1.當程序執行 new User() 時,JVM會查找並加載 User.class 到內存中
2.Jvm 將 .class 加載到內存,自動創建一個Class對象。 Class對象由JVM創建,有且僅有一個,第二次 new User() 不再產生新的 Class 對象
3.一個類對應一個Class對象
反射的本質就是:得到 Class 對象後,反向獲取 User 類的各種信息。比如:構造方法,成員變量,方法等
接下來就看看如何使用 Class 對象獲取到 User 類的各種信息
Java反射基礎API使用
首先,創建一個 User 類
public class User {
}
這個類本身沒有什麼意義,用於演示反射示例
1.獲取Class對象的方式
1.通過 Object 的 getClass()
Object 類中存在 getClass() 方法,所有對象繼承自 Object 因此可以使用 Object 的 getClass() 方法獲取 Class 對象
2.通過任意類的“靜態”class屬性 例如:User.class
3.通過Class類的靜態方法 Class.forName(String name)
public static void main(String args[]) {
//1.使用Object 的 getClass() 方式獲取 Class 對象
User user = new User();
Class uClass1 = user.getClass();
System.out.println("type1:" + uClass1.getName());
//2.使用任意類的"靜態"class 屬性
Class uClass2 = User.class;
System.out.println("type2:" + uClass2.getName());
//3.使用Class靜態方法 Class.
try {
Class uClass3 = Class.forName("demo.reflex.User");//參數是包括包名的完整路徑
System.out.println("type3:" + uClass3.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
結果:
type1:demo.reflex.User
type2:demo.reflex.User
type3:demo.reflex.User
注意:運行期間,只有一個Class對象存在
以上幾種方式都可以獲取Class對象。第一個種能獲取到對象了,一般就不需要使用反射;第二種需要導入包,依賴性太強;第三種較爲常用,也比較符合反射的場景,根據一個完整的類名獲取Class對象
2.通過反射獲取類構造函數 Constructor
User 類
public class User {
User(char ch) {
System.out.println("默認 構造方法 ch:" + ch);
}
public User() {
System.out.println("public 構造方法 無參");
}
public User(String name) {
System.out.println("public 構造方法 name:" + name);
}
public User(String name, int age) {
System.out.println("public 構造方法 name:" + name + " age:" + age);
}
protected User(int age) {
System.out.println("protected 構造方法 age:" + age);
}
private User(boolean flag) {
System.out.println("private 構造方法 flag:" + flag);
}
}
User 類構造方法,包括了 public , private , protected , 默認 類型的構造方法
測試使用
public static void main(String args[]) throws Exception {
//獲取Class
Class cls = Class.forName("demo.reflex.User");
System.out.println("-------獲取所有公共的構造方法---------");
Constructor[] consArray = cls.getConstructors();
for (int i = 0; i < consArray.length; i++) {
System.out.println(consArray[i]);
}
System.out.println("-------獲取所有的構造方法 包括:public,private,protected,默認類型---------");
consArray = cls.getDeclaredConstructors();
for (int i = 0; i < consArray.length; i++) {
System.out.println(consArray[i]);
}
System.out.println("-------獲取 無參 構造方法並調用---------");
Constructor cons = cls.getConstructor();
System.out.println(cons);
cons.newInstance();
System.out.println("-------獲取 private 構造方法並調用---------");
cons = cls.getDeclaredConstructor(boolean.class);
System.out.println(cons);
cons.setAccessible(true);//暴力訪問(忽略訪問修飾符)
cons.newInstance(true);
}
結果輸出
-------獲取所有公共的構造方法---------
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
-------獲取所有的構造方法 包括:public,private,protected,默認類型---------
private demo.reflex.User(boolean)
protected demo.reflex.User(int)
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
demo.reflex.User(char)
-------獲取 無參 構造方法並調用---------
public demo.reflex.User()
public 構造方法 無參
-------獲取 private 構造方法並調用---------
private demo.reflex.User(boolean)
private 構造方法 flag:true
api解釋
1.獲取所有構造方法 返回符合要求的 列表
//獲取所有"公共的"構造方法 public
public Constructor<?>[] getConstructors()
//獲取所有的構造方法 包括: public,private,protected,默認類型
public Constructor<?>[] getDeclaredConstructors()
2.獲取單個構造方法 返回符合要求的 對象
//獲取"公共的"構造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)
/**
*獲取任意訪問類型的構造方法
*
*parameterTypes:形參類型(記住是類型) 例:int.class ,String.class
*/
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
3.調用構造方法
/**
*調用構造方法
*
*initargs:構造方法參數
*/
public T newInstance(Object ... initargs)
newInstance()
方法屬於 Constructor
類,無參構造方法參數可以 null 或者 不填; 對於調用 private 私有構造方法需要調用 cons.setAccessible(true);
表示打破限制,強制訪問,否則出錯
3.通過反射獲取類成員變量 Field
User類
public class User {
public User() {
}
/*成員變量*/
private int age;
public String name;
protected String phone;
boolean isVip;
@Override
public String toString() {
return "[User name=" + name + ", age=" + age + "]";
}
}
測試使用
public static void main(String args[]) throws Exception {
//獲取Class
Class cls = Class.forName("demo.reflex.User");
System.out.println("-------獲取所有公共的成員變量---------");
Field[] fieldArray = cls.getFields();
for (int i = 0; i < fieldArray.length; i++) {
System.out.println(fieldArray[i]);
}
System.out.println("-------獲取所有的成員變量 包括:public,private,protected,默認類型---------");
fieldArray = cls.getDeclaredFields();
for (int i = 0; i < fieldArray.length; i++) {
System.out.println(fieldArray[i]);
}
System.out.println("-------獲取公共屬性---------");
Field field = cls.getField("name");
System.out.println(field);
System.out.println("-------設置屬性並查看值---------");
//根據構造函數 .newInstance() 獲取類對象
Object object = cls.getConstructor().newInstance();
field.set(object, "Ruffian");
//驗證剛剛設置的屬性
User user = (User) object;
System.out.println("查看name屬性值:" + user.name);
System.out.println("-------獲取私有屬性---------");
field = cls.getDeclaredField("age");
System.out.println(field);
System.out.println("-------設置私有屬性並查看值---------");
field.setAccessible(true);
field.set(object, 18);
//驗證剛剛設置的屬性
System.out.println("查看age屬性值:" + user.toString());
}
結果輸出:
-------獲取所有公共的成員變量---------
public java.lang.String demo.reflex.User.name
-------獲取所有的成員變量 包括:public,private,protected,默認類型---------
private int demo.reflex.User.age
public java.lang.String demo.reflex.User.name
protected java.lang.String demo.reflex.User.phone
boolean demo.reflex.User.isVip
-------獲取公共屬性---------
public java.lang.String demo.reflex.User.name
-------設置屬性並查看值---------
查看name屬性值:Ruffian
-------獲取私有屬性---------
private int demo.reflex.User.age
-------設置私有屬性並查看值---------
查看age屬性值:[User name=Ruffian, age=18]
api解釋
1.獲取所有屬性 返回符合要求的 列表
//獲取所有"公共的"屬性 public
public Constructor<?>[] getFields()
//獲取所有的屬性 包括: public,private,protected,默認類型
public Constructor<?>[] getDeclaredFields()
2.獲取單個屬性 返回符合要求的 對象
//獲取"公共的"屬性
public Field getField(String fieldName)
/**
*獲取任意訪問類型的屬性
*
*fieldName:屬性名稱 例: name , age
*/
public Field getDeclaredField(String fieldName)
3.設置屬性值
/**
*1.obj:要設置的字段所在的對象;
*2.value:要爲字段設置的值;
*/
public void set(Object obj,Object value)
field.set(object, "Ruffian");
表示爲屬性設置值: user.name=Ruffian
第一個參數:要設置的字段所在的對象
第二個參數:要爲字段設置的值
至於上述示例中獲取屬性值的方式,是爲了方便校驗通過反射設置值是否成功,一般我們通過反射獲取方法,得到屬性值。下面看看如何通過反射獲取成員方法和調用
4.通過反射獲取成員方法 調用方法 Method
User類
public class User {
public void showName(String name) {
System.out.println("public showName() 參數name = " + name);
}
protected void show() {
System.out.println("public show()");
}
void showDefault() {
System.out.println(" showDefault() 默認無參方法");
}
private String getAge(int age) {
System.out.println("private getAge() 返回值:字符串 參數:int age = " + age);
return "age:" + age;
}
}
使用示例
public static void main(String args[]) throws Exception {
//獲取Class
Class cls = Class.forName("demo.reflex.User");
System.out.println("-------獲取所有方法---------");
//Method[] methodArray = cls.getMethods();
Method[] methodArray = cls.getDeclaredMethods();
for (int i = 0; i < methodArray.length; i++) {
System.out.println(methodArray[i]);
}
System.out.println("-------調用任意訪問類型方法---------");
//根據構造函數 .newInstance() 獲取類對象
Object object = cls.getConstructor().newInstance();
Method method = cls.getDeclaredMethod("getAge", int.class);
method.setAccessible(true);
Object result = method.invoke(object, 18);
System.out.println("方法返回值:" + result.toString());
}
結果輸出
-------獲取所有方法---------
private java.lang.String demo.reflex.User.getAge(int)
public void demo.reflex.User.showName(java.lang.String)
protected void demo.reflex.User.show()
void demo.reflex.User.showDefault()
-------調用任意訪問類型方法---------
private getAge() 返回值:字符串 參數:int age = 18
方法返回值:age:18
api解釋
1.獲取所有方法 返回符合要求的 列表
//獲取所有"公共的"方法 public
public Method[] getMethods()
//獲取所有的方法 包括: public,private,protected,默認類型
public Method[] getDeclaredMethods()
2.獲取單個方法 返回符合要求的 對象
//獲取"公共的"方法
public Method getMethod(String name, Class<?>... parameterTypes)
/**
*獲取任意訪問類型的方法
*
*name:方法名稱 例: getAge , showName
*parameterTypes:方法形參類型(記住是類型) 例:int.class ,String.class
*/
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
3.調用方法
/**
*obj:要調用方法所在的對象
*args:調用方法所需的參數
*/
public native Object invoke(Object obj, Object... args)
method.invoke(object, 18);
表示調用方法: User user=new User(); user.getAge(18);
第一個參數:
要調用方法所在的對象
如果調用方法是靜態的,那麼可以忽略指定的 obj 參數。該參數可以爲 null
第二個參數:
調用方法所需的參數
調用方法所需的形參個數爲 0,則所提供的 args 數組長度可以爲 0 或 null
4.Method 類的一點拓展
瞭解一個方法可以通過: 方法名稱,方法修飾符,返回類型,形參類型/個數
看下示例程序
public static void main(String args[]) throws Exception {
//獲取Class
Class cls = Class.forName("demo.reflex.User");
Method[] methodArray = cls.getDeclaredMethods();
Method method;
for (int i = 0; i < methodArray.length; i++) {
method = methodArray[i];
String name = method.getName();//方法名稱
int modifiers = method.getModifiers();//方法修飾符
Class returnType = method.getReturnType();//方法返回類型
Class[] parameterTypes = method.getParameterTypes();//方法參數類型數組(形參類型)
System.out.println("方法名:" + name + " 修飾符:" + Modifier.toString(modifiers) + " 返回值類型:" + returnType);
//方法參數(形參)
for (int j = 0; j < parameterTypes.length; j++) {
System.out.println("形參" + j + ":" + parameterTypes[j]);
}
System.out.println("");
}
}
運行結果:
方法名:showName 修飾符:public 返回值類型:void
形參0:class java.lang.String
方法名:show 修飾符:protected 返回值類型:void
方法名:showDefault 修飾符: 返回值類型:void
方法名:getAge 修飾符:private 返回值類型:class java.lang.String
形參0:int
5.Java 反射的一點場景使用
5.1 反射結合配置文件使用
User類
public class User {
public void show() {
System.out.println("show方法被調用...");
}
}
在 D 盤目錄下新建一個 pro.txt 文件
className = demo.reflex.User //類名完整路徑
methodName = show //方法名
示例代碼:
public static void main(String args[]) throws Exception {
//獲取Class
Class cls = Class.forName(getValue("className"));
//獲取方法
Method method = cls.getMethod(getValue("methodName"));
Object object = cls.getConstructor().newInstance();//獲取對象
//調用方法
method.invoke(object, null);
}
/***
* 根據key獲取配置文件中 value
*
* @param key
* @return
* @throws IOException
*/
public static String getValue(String key) throws IOException {
Properties pro = new Properties();//獲取配置文件的對象
File file = new File("D:\\pro.txt");//獲取文件
FileReader in = new FileReader(file);//獲取輸入流
pro.load(in);//將流加載到配置文件對象中
in.close();
return pro.getProperty(key);//返回根據key獲取的value值
}
輸出結果:
show方法被調用...
5.2 通過反射越過泛型檢查
還記得 method.invoke(Object obj, Object... args)
方法嗎? 第二個參數表示:調用方法所需參數。這是一個 Object 對象,那麼,嘿嘿嘿~~~
示例代碼:
public static void main(String args[]) throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("AAA");
list.add("BBB");
/**
* 獲取ArrayList的Class對象,反向的調用add()方法,添加數據
*/
Class listClass = list.getClass(); //獲取Class對象
//獲取add()方法
Method m = listClass.getMethod("add", Object.class);
//調用add()方法
m.invoke(list, false);
m.invoke(list, 100);
//遍歷集合
for (Object obj : list) {
System.out.println(obj);
}
}
輸出結果:
AAA
BBB
false
100
上述代碼,ArrayList 指定類型 String 如果直接 list.add(100) 則會包類型錯誤,通過反射調用 m.invoke(obj, obj); 傳入其他類型的對象,從而實現越過泛型的檢查。當然這只是一個例子,實際編碼中,還是需要嚴格按照規範去寫代碼
雖然,博文比較長,但是內容真的很簡單,純屬無腦式的API調用,起碼通過學習反射 API 的使用先了解反射的基礎。這也是爲什麼文章開篇說反射簡單了一逼,就是爲了鼓舞大家有勇氣面對這些知識點
由於本文都是API的簡單使用,也都參考網絡資料,就不一一附上連接了。