Java反射,java反射機制
一、 概念
一句話說明反射“反射就是把Java類中的各種成分映射成相應的Java類”。如果你對反射足夠的瞭解,相信你一定會對這句話有很多共鳴。
換句話說:一個類中的每個成員都可以用相應的反射API類的一個執行個體對象來表示(如),通過調用Class類的方法(getField、getMethod、getConstructor等)可以得到這些執行個體對象。
也就是說一個Java類用一個Class類的對象表示,一個類中的組成部分:成員變數、方法、構造方法、包等等資訊也用一個個的Java類來表示。就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示Java類的Class類,顯然要提供一系列的方法,來獲得其中的變數,方法,構造方法,修飾符,包等資訊,這些資訊就是用相應類的執行個體對象來表示,它們是Field、Method、Contructor、Package等等.
二、 理解Class類
Java程式中各個Java類屬於同一類事物,描述這一類事物的Java的類名就是Class(注意區別小寫class)。
例如,Person類代表人,它的執行個體對象就是“張三”、“李四”這樣一個個具體的人;同理,Class類代表Java類,它的執行個體對象對應各個類在記憶體中的位元組碼,如:Person類的位元組碼、ArrayList類的位元組碼等。
獲得位元組碼(Class類型)的三種方式如下:
根據理解位元組碼的概念:
三、 幾種常用的反射應用
下面我們簡單介紹構造方法、成員變數、成員方法的反射應用:
3.1 構造方法的反射應用(Constructor類)
Contructor類代表某個類中的一個構造方法。
得到某個類所有的構造方法:
例:Contructor[ ]constructors=Class.forName(“java.lang.String”).getConstructors();
得到某個類的某一個構造方法:
例:Contructor[ ]constructors=Class.forName(“java.lang.String”).getConstructors(StringBuffer.class);
範例程式碼如下:
package com.tgb.constructorReflect;import java.lang.reflect.Constructor;public class TestConstructorReflect {public static void main(String[] args) throws Exception {// 1、通常情況(非反射)String str1 = new String(new StringBuffer("zhipeng"));// 2、反射情況Constructor<String> constructor = String.class.getConstructor(StringBuffer.class);String str2 = (String) constructor.newInstance(new StringBuffer("zhipeng"));}}
一個類有多個構造方法,用什麼方式可以區分清楚想得到其中的哪個方法呢?根據參數的個數和類型,例如,CIass.getMethod(name,CIass…args)中的args參數就代表所要擷取的那個方法的各個參數的類型的列表。重點:參數類型用什麼方式表示?用Class執行個體對象。例如:int.Class、int[ ].Class等。
Class.newInstance()方法內部就是先得到類的預設的構造方法對象的位元組碼,然後用它建立執行個體對象。
3.2 成員變數的反射應用(Field類)
Field類代表某一個類中的成員變數。
範例程式碼1如下:
先定義一個ReflectPoint類
public class ReflectPoint {private int x;// 注意x為private的public int y;public ReflectPoint(int x, int y) {super();this.x = x;this.y = y;}}
然後我們先拿到ReflectPoint的類的位元組碼,然後取出它某一屬性的位元組碼,進而就可以得到這個類任何一個對象的相應的屬性值:
import java.lang.reflect.Field;public class TestFieldReflect {/** * author: zhipeng */public static void main(String[] args) throws Exception {ReflectPoint pt1 = new ReflectPoint(1, 2);ReflectPoint pt2 = new ReflectPoint(10, 12);Field fieldY = ReflectPoint.class.getField("y");// 拿到ReflectPoint【類】的是屬性名稱為y的定義(位元組碼)System.out.println("pt1--->y--->" + fieldY.get(pt1));// 輸出對象pt1的屬性y:2System.out.println("pt2--->y--->" + fieldY.get(pt2));// 輸出對象pt2的屬性y:12Field fieldX = ReflectPoint.class.getDeclaredField("x");// x為private的,所以要用getDeclaredFieldfieldX.setAccessible(true);// 暴力反射System.out.println(fieldX.get(pt1));// 輸出對象pt1的屬性x:1}}
上面我們通過【FieldfieldY = ReflectPoint.class.getField("y");】拿到的Field對象是對應到類上面的成員變數,還是對應到對象上的成員變數呢?我們分析,類只有一個,而類的執行個體對象有多個,如果是與對象關聯,那麼關聯的是哪個對象呢?所以fieldX代表的是x的定義,而不是具體的x變數。也就是說我們拿到的是x在ReflectPoint.class中對應的定義(位元組碼),要用它去擷取某一個執行個體對象身上對應的屬性值。
範例程式碼2如下:
首先定義一個Student類
/** * @author wangzhipeng */public class Student {// 1、定義兩個Fieldpublic String userName;public String password;// 2、構造方法,給兩個Field賦值public Student(String username, String password) {this.userName = username;this.password = password;}// 3、重寫它的toString方法@Overridepublic String toString() {return "使用者名稱:" + this.userName + " 密碼:" + this.password;}}
然後,我們通過反射實現,將任意一個Student對象中屬性值中的字母“a”改為“【b】”,代碼如下:
import java.lang.reflect.Field;public class TestChangeStringValue {public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {Student zhipeng = new Student("akobe", "a123");System.out.println(zhipeng);// 修改屬性前,輸出:使用者名稱:akobe 密碼:a123ChangeStrValue(zhipeng);// 調用更改屬性的方法System.out.println(zhipeng);// 修改屬性後,輸出:使用者名稱:【b】kobe 密碼:【b】123}public static void ChangeStrValue(Object object) throws IllegalArgumentException, IllegalAccessException {// 1、通過反射,拿到一個類所有的屬性Field[] fields = object.getClass().getDeclaredFields();// 2、迴圈變數屬性,如果屬性類型為String則將其屬性中的字母"a"改為"【b】"for (Field field : fields) {if (field.getType() == String.class) {String oldValue = (String) field.get(object);String newValue = oldValue.replace("a", "【b】");field.set(object, newValue);}}}}
3.3 成員方法的反射應用(Method類)
Method類代表某個類中的一個成員方法。
範例程式碼1如下:
首先定義一個Student類,裡面有一個靜態和一個非靜態方法:
/** * @author wangzhipeng */public class Student {// 1、定義一個靜態方法Study()public static void Study() {System.out.println("static method--->Study()");}// 2、定義一個非靜態方法(String songName)public void Sing(String songName) {System.out.println("not static method--->Sing():" + songName);}}
然後我們反射執行這兩個方法,注意它們的差異:
import java.lang.reflect.Method;public class MethodReflectTest {public static void main(String[] args) throws Exception {// 0、獲得Student的classClass clazz = Student.class;// 1、反射執行Student類的【非靜態】方法:Sing(String songName)Method methodSing = clazz.getMethod("Sing", String.class);// 第二個參數為參數的【類型】methodSing.invoke(new Student(), "冰雨");// 非靜態方法屬於某個對象,所以第一個參數為一個Student的對象。// 2、反射執行Student類的【靜態】方法:static void Study()Method methodStudy = clazz.getMethod("Study", null);methodStudy.invoke(null, null);// 靜態方法屬於類,所以第一個參數為null;而這個方法沒有參數,所以第二個方法為null}}
範例程式碼2如下:
我們通過反射來執行某一類的main方法.
首先定義一個Teacher類:
public class Teacher {// 定義static的main方法public static void main(String[] args) {for (String arg : args) {System.out.println(arg);}}}
然後我們寫一個工具類,執行任何類的main方法:
// 工具類,執行傳入的類的main方法public static void ExecuteMainMehod(Class clazz, String[] argStrings) throws Exception {Method teacherMainMethod = clazz.getMethod("main", String[].class);// teacherMainMethod.invoke(null, new String[] { "aaa", "bbb"// });//報wrong number of arguments(即參數個數異常)teacherMainMethod.invoke(null, new Object[] { argStrings });// 需要將argStrings包一層再傳入,因為jdk1.4預設會將該參數拆箱}
測試,執行Teacher類的main方法:
public static void main(String[] args) throws Exception {String[] argStrings = new String[] { "aaa", "bbb" };ExecuteMainMehod(Teacher.class, argStrings);// 輸出:aaa// bbb}
四、 用途
反射從java1.2就有了。它最大的用途應該是用來寫架構,為什麼呢?首先我們要理解,我們用架構(例如:Spring),其實是架構在調用我們的類。但是,架構已經寫好了,而被調用的(我們的)類卻還沒寫,這與我們的順序思維有些不同。架構之所以能夠調用還不存在的類,就意味著它離不開反射。
當你的類還沒有寫時,我就能調用,只要你運行時寫出來就行,這就是反射所做的事情。例如Spring:
類似上面的spring配置,我們在所有的架構設定檔中都要配置類的名稱【屬性的名稱】等,不用想就知道是為了反射。
五、 總結
反射就是把Java類中的各種成分映射成相應的Java類,例如:將構造方法映射為Constructor類;將屬性對應為Field類;將方法映射為Method類。
我們接觸到的反射的最大的用途應該是用來寫架構,我們在Spring、Struts等等架構的設定檔中都要配置我們的業務類的類名、屬性名稱等,就是為了運行時反射出它的對象或者屬性值、方法等。因為架構都是要調用我們那些還沒有寫的不存在的業務類,所以它離不開反射。