1.理解反射的基礎:Class類
眾所周知Java有個Object 類靶壮,是所有Java 類的繼承根源,其內聲明了數個應該在所有Java 類中被改寫的方法:hashCode()员萍、equals()腾降、clone()、toString()碎绎、getClass()等螃壤。其中getClass()返回一個Class 對象抗果。
Class 類十分特殊。它和一般類一樣繼承自Object映穗,其實體用以表達Java程序運行時的classes和interfaces,也用來表達enum幕随、array蚁滋、primitive Java types(boolean, byte, char, short, int, long, float, double)以及關鍵詞void。當一個class被加載赘淮,或當加載器(class loader)的defineClass()被JVM調用辕录,JVM 便自動產生一個Class 對象。如果您想借由“修改Java標準庫源碼”來觀察Class 對象的實際生成時機(例如在Class的constructor內添加一個println())梢卸,這樣是行不通的走诞!因為Class并沒有public constructor。
Class是Reflection故事起源蛤高。針對任何您想探勘的類厨埋,唯有先為它產生一個Class 對象屹徘,接下來才能經由后者喚起為數十多個的Reflection APIs。這些APIs將在稍后的探險活動中一一亮相。
Java類用于描述一類事物的共性据悔,該類事物有什么屬性,至于這個類的屬性值是什么导犹,則是由這個類的對象來確定尔艇,不同的實例對象有不同的屬性值。java程序中的各個類喜庞,他們也屬于同一類事物诀浪,也可以用一個類來描述這類事物,這個類就是Class延都,要注意和小寫的class關鍵字的區(qū)別雷猪。Class類描述那些方面的信息呢?類的名字晰房,類的訪問屬性春宣,類所屬于的包名,字段名稱的列表嫉你,方法名稱的列表月帝,等等,學習反射幽污,首先要明白這個Class類嚷辅。
對比提問1:眾多的人用一個什么類表示?眾多的java類用什么表示距误?
人-》Person
Java類-》Class
此時很明顯的體現(xiàn)了java面向對象的思想(一切皆為對象)簸搞。
對比提問2:Person類代表人扁位,他的實例對象就是張三,李四這樣一個個具體的人趁俊,Class類代表java類域仇,那么他的實例對象又分別對應什么呢?
對應的是各個類在內存中的字節(jié)碼(一個類有且僅有一份)寺擂,例如暇务,Person類的字節(jié)碼,ArrayList類的字節(jié)碼等等怔软。(字節(jié)碼就是類被加載到jvm中內存中的Class類的實例垦细,然后利用這個字節(jié)碼復制一個個指定類的對象)。
一個類被類加載器加載到內存中挡逼,占用一片存儲空間括改,這個空間里邊的內容就是類的字節(jié)碼,不同的類的字節(jié)碼是不同的家坎,所以他在內存中的內容是不同的嘱能,這一個個的空間分別用一個個對象來表示,這些對象顯示具有相同的類型虱疏,這個類型是什么呢焰檩?就是 Class類。
三種獲取Class對象的方法:
不能用Class c = new Class(),沒有這個構造方法的订框,應該采取下邊的三種方法:
1). Person.class 類名.class的方式;
2). Person p = new Person; p.getClass(); person類的實例getClass的方式;
3). Class.forName(“類名(全路徑)”); 這個如果在jvm內存中沒有找到指定類的字節(jié)碼析苫,就先將指定類加載到jvm內存中,然后在返回指定的類的Class實例穿扳,如果jvm中存在就直接返回衩侥。
需要注意的是:一般實現(xiàn)反射的話都是使用第三種方式Class.forName(“”).因為這個參數可以寫成變量傳進來,此時就可以讀取配置文件的字符串來動態(tài)的獲取相關類的字節(jié)碼實例矛物。
/**
* 獲取一個類的字節(jié)碼對象的三種方法 同一個類茫死,類實例(字節(jié)碼對象)在jvm中有且只有一份
*
* @throws ClassNotFoundException
*/
@Test
public void test1() throws ClassNotFoundException {
String str = "abc";
// 表示返回此對象運行時類的 Class對象
Class<? extends String> cls1 = str.getClass();
// 編譯器通過String就可以確定返回String類型的Class對象。
Class<String> cls2 = String.class;
// 給編譯器看的履羞,編譯器并不知道通過forName會返回什么類型的Class對象
Class<?> cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2); // true
System.out.println(cls1 == cls3); // true
}
由輸出結果可知峦萎,每個類不管使用三種方式中的哪種方式來獲取Class對象都是一份,從而證明了內存中同一個類的Class對象有且只有一份忆首。
9個預定義的class對象:八種基本數據類型和void關鍵字爱榔。
/**
* 預定義的Class對象
*/
@Test
public void test2() {
Class<String> cls2 = String.class;
// primitive是原始的意思,判定指定的 Class對象是否表示一個基本類型(原始類型)
System.out.println(cls2.isPrimitive());
// 判定指定的 Class 對象是否表示一個基本類型
System.out.println(int.class.isPrimitive());
// 基本類型和對應包裝類Class對象不是同一個
System.out.println(int.class == Integer.class); //false int和Integer包裝類不是同一份字節(jié)碼
/*
* Integer.TYPE代表的是integer包裝的基本類的字節(jié)碼
* 根據文檔:表示基本類型 int 的 Class 實例
*/
System.out.println(int.class == Integer.TYPE); //true
//數組有對應的數組類(Array類)
System.out.println(int[].class.isPrimitive()); //false
// 是不是數組類class對象
System.out.println(int[].class.isArray());
}
2.反射的概念
JAVA反射機制是在運行狀態(tài)中糙及,對于任意一個類详幽,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意方法和屬性唇聘;這種動態(tài)獲取信息以及動態(tài)調用對象方法的功能稱為java語言的反射機制版姑。
JAVA反射(放射)機制:“程序運行時,允許改變程序結構或變量類型迟郎,這種語言稱為動態(tài)語言”剥险。從這個觀點看,Perl宪肖,Python表制,Ruby是動態(tài)語言,C++匈庭,Java夫凸,C#不是動態(tài)語言浑劳。但是JAVA有著一個非常突出的動態(tài)相關機制:Reflection阱持,用在Java身上指的是我們可以于運行時加載、探知魔熏、使用編譯期間完全未知的classes衷咽。換句話說,Java程序可以加載一個運行時才得知名稱的class蒜绽,獲悉其完整構造(但不包括methods定義)镶骗,并生成其對象實體、或對其fields設值躲雅、或喚起其methods.
Java反射機制主要提供了以下功能: 在運行時判斷任意一個對象所屬的類鼎姊;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法相赁;在運行時調用任意一個對象的方法相寇;生成動態(tài)代理。
反射就是把java類中的各個成分映射成相應的java類钮科。
例如:一個java類中用一個Class類的對象來表示唤衫,一個類中的組成部分:成員變量,方法绵脯,構造方法佳励,包等信息也用一個個的java類來表示,就像汽車是一個類蛆挫,汽車中的發(fā)動機赃承,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一些方法來獲取其中的變量悴侵,方法楣导,構造方法,修飾符畜挨,包等信息筒繁,這些信息就是用相應類的實例對象來表示噩凹,他們是Filed,Method毡咏,Constructor驮宴,Package等等。(可以查看Class類的api就可以了解相關的方法)呕缭。
一個類的每個成員都可以用相應的反射API類的一個實例對象來表示堵泽,通過調用Class類的方法可以得到這些實例對象后,得到這些實例對象后有什么用呢恢总?怎么用呢迎罗?這才是要點。
反射在很多地方都能用到片仿,比如Spring(一個開放源代碼的設計層面框架)纹安,Struts(一個基于MVC設計模式的Web應用框架),JUnit(一個Java語言的單元測試框架)等一些框架中砂豌,反射是框架設計的靈魂厢岂。
3.構造方法的反射
Constructor類代表某個類中的一個構造方法。
/**
* 構造方法的反射
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
@Test
public void test3() throws NoSuchMethodException, SecurityException,
InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
Class<String> clazz = String.class;
//獲取String類的所有構造方法
Constructor<?>[] constructors = clazz.getConstructors();
/*
* 通過構造函數反射實例化對象
*/
String str1 = new String(new StringBuffer("adc"));
Constructor<String> constructor1 = String.class
.getConstructor(StringBuffer.class);
String str2 = constructor1.newInstance(new StringBuffer("abc"));
//使用默認的構造方法實例化對象阳距,newInstance
String str3 = String.class.newInstance();
}
使用newInstance方法來實例化對象的情況塔粒,newInstance方法先得到相應類的默認的構造方法,然后使用該默認的構造方法來實例化對象筐摘。
通過查看源碼知:java會用緩存機制來緩存默認的構造方法實例卒茬,由此可以看出用了緩存說明獲取Constructor對象是一個很耗時的操作。
擴展:因為反射機制的存在咖熟,所以單例模式并不能完全保證單例對象的唯一性圃酵。如何保證單例防反射攻擊,請看這篇: 單例模式--反射--防止序列化破壞單例模式
4.成員變量的反射
Filed類代表某個類中的一個成員變量球恤,即某個類的字節(jié)碼對象的字段對象辜昵,不和具體的類的實例對應。
public class ReflectPoint {
private Date birthday = new Date();
private int x;
public int y;
public String str1;
public String str2;
public String str3;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
public ReflectPoint(String str1, String str2, String str3) {
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
@Override
public String toString() {
return str1 + ":" + str2 + ":" + str3;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
/**
* 成員變量的反射
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
@Test
public void test4() throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
ReflectPoint pt1 = new ReflectPoint(3, 5);
Class<? extends ReflectPoint> clazz = pt1.getClass();
/*
* 獲取指定的字段對象
* fieldY的值是多少咽斧?是5,錯堪置!fieldY不是對象身上的變量,而是類上张惹,要用它去取某個對象上對應的值
*/
Field fieldY = clazz.getField("y");
//獲取pt1對象上的y字段的值
System.out.println("變量Y的值:" + fieldY.getInt(pt1));
/*
* getField只能獲取public和defalut修飾的字段舀锨,此時x是私有變量。
*/
/*
* Field fieldX = clazz.getField("x");
* System.out.println(fieldX.getInt(pt1));
*/
/*
* getDeclaredFields可以獲取類中所有的成員變量 包括私有的變量
*/
Field[] fields = clazz.getDeclaredFields();
System.out.println("成員變量個數:" + fields.length);
/*
* 獲取私有變量x的值
* 雖然獲取到x了宛逗,但是沒有權限訪問其中的值坎匿,所以需要設置x字段對象為可以訪問的。
*/
Field fieldX = clazz.getDeclaredField("x");
fieldX.setAccessible(true);
System.out.println("變量X的值:" + fieldX.getInt(pt1));
}
/**
* 成員變量反射的綜合案例 將任意一個對象中的所有String類型的成員變量所對應的字段內容中的“b”改成”a”
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
@Test
public void test5() throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
ReflectPoint pt1 = new ReflectPoint("ball", "basketball", "itcast");
System.out.println("修改前:");
System.out.println(pt1.str1);
System.out.println(pt1.str2);
System.out.println(pt1.str3);
Class<? extends ReflectPoint> clazz = pt1.getClass();
// /Field fieldX = clazz.getDeclaredField("x");
Field[] fields = clazz.getDeclaredFields(); //獲取所有的字段 包括私有字段
for (Field field : fields) {
/*
* 獲取字段的類型(返回的是類型的字節(jié)碼實例) 判斷是否和String類的字節(jié)碼相等就能判斷是否是String類型的字段了。
* 因為字節(jié)碼只有一個,所以使用 == 比使用equeals更能體現(xiàn)意義
*/
Class<?> filedType = field.getType();
if (filedType == String.class) {
field.setAccessible(true);
String oldValue = (String) field.get(pt1);
String newValue = oldValue.replace("b", "a");
field.set(pt1, newValue); //重新將替換后的新值set到相關到對象中去替蔬。
}
}
System.out.println("修改后:");
System.out.println(pt1.str1);
System.out.println(pt1.str2);
System.out.println(pt1.str3);
}
結論:
從上可以看出對于一些對象告私,可以使用反射將其中的值都改掉,Spring實例化里邊的值和初始化對象的值都是如此實現(xiàn)的承桥。
5.成員方法的反射
Method類代表某個類中的一個成員方法驻粟。
/**
* 成員方法的反射
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void test6() throws NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
String str = "abc";
//獲取指定方法的Method類的實例
Method method = String.class.getMethod("charAt", int.class);
//調用指定實例的方法,并傳參
//如果是Method代表的是靜態(tài)方法凶异,那么第一參數為null
System.out.println(method.invoke(str, 1));
}
6.對接收數組參數的成員方法進行反射
問題:
用反射的方式執(zhí)行某個類中的main方法,寫一個程序蜀撑,根據用戶提供的類名,去執(zhí)行該類的main方法剩彬。如果使用傳統(tǒng)的方式執(zhí)行main方法可以直接Test.main(new String[]{}); 但是不能動態(tài)指定類名(用戶提供的)酷麦,因為傳統(tǒng)的方式都已經編譯了不能再改了。
/**
* 對接收數組參數的成員方法進行反射 用反射的方式執(zhí)行某個類中的main方法,寫一個程序喉恋,根據用戶提供的類名沃饶,去執(zhí)行該類的main方法。
*
* @throws ClassNotFoundException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void test7() throws ClassNotFoundException, NoSuchMethodException,
SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
String mainStr = "mytest.TestArguments";
Method mainMethod = Class.forName(mainStr).getMethod("main",
String[].class);
mainMethod.invoke(null, new String[] { "a", "b", "c" });
}
/**
* 測試反射main方法的類
*/
class TestArguments {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
上述代碼報錯:java.lang.IllegalArgumentException: wrong number of arguments
明顯是參數個數錯誤瀑晒,main方法確實需要一個參數绍坝,而我也傳了一個參數徘意,為什么會錯呢苔悦?
因為在為invoke傳遞數組類型的參數的時候,按照jdk1.5的做法椎咧,整個數組是一個參數玖详,而按照jdk1.4的語法,會將數組拆開勤讽,每個元素對應一個參數蟋座,所以當把一個字符串數組作為一個參數傳遞給invoke方法時,javac會按照哪種方法進行處理呢脚牍?jdk1.5肯定要兼容jdk1.4的做法向臀,會先按照1.4的語法處理,即把數組拆開為每一個元素作為一個參數诸狭。所以如果按照上邊的方式會出現(xiàn)異常券膀。可以采用下邊的方式:
/**
* 對接收數組參數的成員方法進行反射 用反射的方式執(zhí)行某個類中的main方法,寫一個程序驯遇,根據用戶提供的類名芹彬,去執(zhí)行該類的main方法。
* @throws ClassNotFoundException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void test7() throws ClassNotFoundException, NoSuchMethodException,
SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
String mainStr2 = "mytest.TestArguments";
Method mainMethod2 = Class.forName(mainStr2).getMethod("main",
String[].class);
/*
* jdk1.4的做法
* 重新包裝字符串數組為一個object數組叉庐,此時按照1.4的方式拆開之后只有一個參數就是里邊的string數組
*/
mainMethod2
.invoke(null, new Object[] { new String[] { "a", "b", "c" } });
/*
* jdk1.5的做法
* 重新包裝字符串數組為一個object舒帮,此時給invoke方法傳遞的參數不是數組,編譯器不會去拆解了,
* (數組也是Object)
*/
mainMethod2.invoke(null, (Object) new String[] { "a", "b", "c" });
}
7.數組與Object的關系及數組的反射類型
根據文檔中class的解釋玩郊,對于數組的Class對象實例肢执,具有相同維數(一維數組二維數組等)和元素類型的屬于同一個類型,即具有相同的Class實例對象译红。中文文檔的說明:所有具有相同元素類型和維數的數組都共享該 Class 對象蔚万。
int[] a1 = new int[] { 1, 2, 3 };
Integer[] a11 = new Integer[] { 1, 2, 3 };
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[] { "a", "b", "c" };
System.out.println(a1.getClass() == a2.getClass()); // true
// 都是一維數組,但是數組元素類型不一致临庇,直接編譯不通過反璃。
// System.out.println(a1.getClass() == a4.getClass());
// 數組元素類型以致,但是維數不同假夺,直接編譯不通過淮蜈。
// System.out.println(a1.getClass() == a3.getClass());
數組的反射類型的name:
/*
* 基本類型的數組getName
*/
//[I
System.out.println("int[]:" + a1.getClass().getName());
//int
System.out.println("int:" + int.class.getName());
/*
* 引用類型的數組getName
*/
// [Ljava.lang.String;
System.out.println("String[]:" + a4.getClass().getName());
// java.lang.String
System.out.println("String:" + String.class.getName());
/*
* 與getName相對應的是forName
* 根據數組類型的Name類獲取Class
*/
// 基本類型
Class<?> c1 = Class.forName("[I");
System.out.println(c1.isArray()); // true
// 引用類型
Class<?> c2 = Class.forName("[Ljava.lang.String;");
System.out.println(c2.isArray()); // true
對于"[I"中,[表示數組已卷,I表示數組元素類型是int梧田。
具體I代表什么參考getName方法的api注釋:
基礎類型:
Element Type Encoding
boolean Z
byte B
char C
double D
float F
int I
long J
short S
引用類型:
Element Type Encoding
class or interface Lclassname;
例子:
String.class.getName()
returns "java.lang.String"
byte.class.getName()
returns "byte"
(new Object[3]).getClass().getName()
returns "[Ljava.lang.Object;"
(new int[3][4][5][6][7][8][9]).getClass().getName()
returns "[[[[[[[I"
getSimpleName方法
// String
System.out.println(String.class.getSimpleName());
// int[]
System.out.println(a1.getClass().getSimpleName());
8.數組的反射應用
數組不是類,但是實現(xiàn)了Object中的方法侧蘸。
按照Java語言規(guī)范的說法裁眯,Java數據類型分為兩大類:基本數據類型和復合數據類型,其中復合數據類型包括數組讳癌、類和接口穿稳。
Array類(Array工具類)完成對數組的反射,是代表了對于數組類的描述晌坤。文檔中說明:Array 類提供了動態(tài)創(chuàng)建和訪問 Java 數組的方法逢艘。
擴展 : java.lang.reflect包