前言
Java反射機制很早的時候就有耳聞,期間也會去看看相關資料份汗,但是又很快會忘記绊起,所以精拟,寫一篇Blog來加深記憶與理解!虱歪!
Java反射的定義
Java反射機制是指在運行狀態(tài)(非編譯)中蜂绎,對于任意一個類,都能夠知道這個類的所有屬性和方法笋鄙;對于任意一個對象师枣,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制萧落。 用一句話總結(jié)就是反射可以實現(xiàn)在運行時可以知道任意一個類的屬性和方法践美。
Java反射機制的優(yōu)點與缺點
在說明反射的優(yōu)缺點前,我們需要知道一個概念:靜態(tài)編譯和動態(tài)編譯
靜態(tài)編譯:在編譯時確定類型找岖,綁定對象陨倡。
動態(tài)編譯:運行時確定類型,綁定對象许布。動態(tài)編譯最大限度發(fā)揮了java的靈活性兴革,體現(xiàn)了多態(tài)的應用,可以降低類之間的藕合性爹脾。
優(yōu)點
可以實現(xiàn)動態(tài)創(chuàng)建對象和編譯帖旨,體現(xiàn)出很大的靈活性,特別是在J2EE的開發(fā)中它的靈活性就表現(xiàn)的十分明顯灵妨。比如,一個大型的軟件落竹,不可能一次就把把它設計的很完美泌霍,當這個程序編譯后,發(fā)布了,當發(fā)現(xiàn)需要更新某些功能時朱转,我們不可能要用戶把以前的卸載蟹地,再重新安裝新的版本,假如這樣的話藤为,這個軟件肯定是沒有多少人用的怪与。采用靜態(tài)的話,需要把整個程序重新編譯一次才可以實現(xiàn)功能的更新缅疟,而采用反射機制的話分别,它就可以不用卸載,只需要在運行時才動態(tài)的創(chuàng)建和編譯存淫,就可以實現(xiàn)該功能耘斩。
缺點
對性能有影響。使用反射基本上是一種解釋操作桅咆,我們可以告訴JVM括授,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執(zhí)行相同的操作岩饼。
Class類和類類型
Java反射機制中最基礎的類就是Class類了荚虚。(There is a class named Class)
Class 類是什么類呢?
我們平時所說的類其實也是一個對象籍茧,都是Class這個類的對象版述。換句話說:(平時所說的)類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)硕糊。
如何創(chuàng)建Class對象
對于普通的對象院水,我們一般都會這樣創(chuàng)建和表示:
Code code1 = new Code();
上面說了,所有的類都是Class的對象简十,那么如何表示呢檬某,可不可以通過如下方式呢:
Class c = new Class();
我們?nèi)タ纯丛创a中的Class類
/*
* Constructor. Only the Java Virtual Machine creates Class
* objects.
*/
private Class() {}
可以看到他的構(gòu)造函數(shù)是私有的而且只能由JVM來構(gòu)建對象,我們知道螟蝙,為了防止在其他地方構(gòu)建類恢恼,我們就會把構(gòu)造函數(shù)設為私有的。所以胰默,這里不能用常規(guī)的方法構(gòu)建Class的對象场斑,那總是有辦法的。下面我們就來看看有什么方法可以創(chuàng)建Class的對象呢牵署?Java給我們提供了三種方法:
Class c1 = Code.class;
//這說明任何一個類都有一個隱含的靜態(tài)成員變量class漏隐,這種方式是通過獲取類的靜態(tài)成員變量class得到的
Class c2 = code1.getClass();
//code1是Code的一個對象,這種方式是通過一個類的對象的getClass()方法獲得的
Class c3 = Class.forName("com.trigl.reflect.Code");
//這種方法是Class類調(diào)用forName方法奴迅,通過一個類的全量限定名獲得
這里青责,c1、c2、c3都是Class的對象脖隶,他們是完全一樣的扁耐,而且有個學名,叫做Code的類類型(class type)产阱。
這里就讓人奇怪了婉称,前面不是說Code是Class的對象嗎,而c1构蹬、c2王暗、c3也是Class的對象,那么Code和c1怎燥、c2瘫筐、c3不就一樣了嗎?為什么還叫Code什么類類型铐姚?這里不要糾結(jié)于它們是否相同策肝,只要理解類類型是干什么的就好了,顧名思義隐绵,類類型就是類的類型之众,也就是描述一個類是什么,都有哪些東西依许,所以我們可以通過類類型知道一個類的屬性和方法棺禾,并且可以調(diào)用一個類的屬性和方法,這就是反射的基礎峭跳。
來個demo吧膘婶!
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//第一種:Class c1 = Code.class;
Class class1=ReflectDemo.class;
System.out.println(class1.getName());
//第二種:Class c2 = code1.getClass();
ReflectDemo demo2= new ReflectDemo();
Class c2 = demo2.getClass();
System.out.println(c2.getName());
//第三種:Class c3 = Class.forName("com.trigl.reflect.Code");
Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
System.out.println(class3.getName());
}
}
執(zhí)行結(jié)果:
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
反射相關操作
前面我們知道了怎么獲取Class,那么我們可以通過這個Class干什么呢蛀醉?
總結(jié)如下:
1.獲取與調(diào)用成員方法Method
2.獲取操作成員變量Field
3.獲取構(gòu)造函數(shù)Constructor
下面來分別介紹著幾種技能:
1.獲取與調(diào)用成員方法:
1.單獨獲取某一個方法是通過Class類的以下方法獲得的:
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到該類所有的方法悬襟,不包括父類的
public Method getMethod(String name, Class<?>... parameterTypes) // 得到該類所有的public方法,包括父類的
兩個參數(shù)分別是方法名和方法參數(shù)類的類類型列表拯刁。
例如類A有如下一個方法:
public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"歲");
}
現(xiàn)在知道A有一個對象a脊岳,那么就可以通過:
Class c = Class.forName("com.tengj.reflect.Person"); //先生成class
Object o = c.newInstance(); //newInstance可以初始化一個實例
Method method = c.getMethod("fun", String.class, int.class);//獲取方法
method.invoke(o, "tengj", 10); //通過invoke調(diào)用該方法,參數(shù)第一個為實例對象垛玻,后面為具體參數(shù)值
完整代碼如下:
public class Person {
private String name="Simon";
private int age;
private String msg="hello wrold";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
private Person(String name) {
this.name = name;
System.out.println(name);
}
public void fun() {
System.out.println("fun");
}
public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"歲");
}
}
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Object o = c.newInstance();
Method method = c.getMethod("fun", String.class, int.class);
method.invoke(o, "tengj", 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行結(jié)果:
我叫tengj,今年10歲
通過Class類獲取Person類類型(上面提到過的)割捅,在通過newInstance()方法獲取Person的實例。這里需要借助Method的invoke()方法調(diào)用fun()方法帚桩。
2.獲取所有方法的數(shù)組:
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 得到該類所有的方法亿驾,不包括父類的
或者:
Method[] methods = c.getMethods();// 得到該類所有的public方法,包括父類的
然后循環(huán)這個數(shù)組就得到每個方法了:
for (Method method : methods)
完整代碼如下:
person類跟上面一樣账嚎,這里以及后面就不貼出來了颊乘,只貼關鍵代碼
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods();
for(Method m:methods){
String methodName= m.getName();
System.out.println(methodName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行結(jié)果:
getName
setName
setAge
fun
fun
getAge
這里如果把c.getDeclaredMethods();改成c.getMethods();執(zhí)行結(jié)果如下参淹,多了很多方法醉锄,以為把Object里面的方法也打印出來了乏悄,因為Object是所有類的父類:
getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
2.獲取與調(diào)用成員變量:
想一想成員變量中都包括什么:成員變量類型+成員變量名
類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象恳不,所以我們通過java.lang.reflect.Field里面封裝的方法來獲取這些信息檩小。
單獨獲取某個成員變量,通過Class類的以下方法實現(xiàn):
public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變量烟勋,不包括其父類的變量
public Field getField(String name) // 獲得該類自所有的public成員變量规求,包括其父類變量
參數(shù)是成員變量的名字。
例如一個類A有如下成員變量:
private int n;
如果A有一個對象a卵惦,那么就可以這樣得到其成員變量:
Class c = a.getClass();
Field field = c.getDeclaredField("n");
這樣我們就可以獲取他的成員變量了阻肿,當然,通過Field類我們還可以對變量進行操作沮尿。
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
//獲取成員變量
Field field = c.getDeclaredField("msg"); //因為msg變量是private的丛塌,所以不能用getField方法
Object o = c.newInstance();
field.setAccessible(true);//設置是否允許訪問,因為該變量是private的畜疾,所以要手動設置允許訪問赴邻,如果msg是public的就不需要這行了。
Object msg = field.get(o);
System.out.println(msg);
field.set(o,"hello Android");
Object msg1 = field.get(o);
System.out.println(msg1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果:
hello wrold
hello Android
同樣啡捶,如果想要獲取所有成員變量的信息姥敛,可以通過以下幾步
1.獲取所有成員變量的數(shù)組:
Field[] fields = c.getDeclaredFields();
2.遍歷變量數(shù)組,獲得某個成員變量field
for (Field field : fields)
完整代碼:
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Field[] fields = c.getDeclaredFields();
for(Field field :fields){
System.out.println(field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果:
name
age
msg
獲取構(gòu)造函數(shù)
最后再想一想構(gòu)造函數(shù)中都包括什么:構(gòu)造函數(shù)參數(shù)
同上瞎暑,類的成構(gòu)造函數(shù)也是一個對象彤敛,它是java.lang.reflect.Constructor的一個對象,所以我們通過java.lang.reflect.Constructor里面封裝的方法來獲取這些信息了赌。
單獨獲取某個構(gòu)造函數(shù),通過Class類的以下方法實現(xiàn):
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 獲得該類所有的構(gòu)造器墨榄,不包括其父類的構(gòu)造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 獲得該類所以public構(gòu)造器,包括父類
這個參數(shù)為構(gòu)造函數(shù)參數(shù)類的類類型列表揍拆。
例如類A有如下一個構(gòu)造函數(shù):
public A(String a, int b) {
// code body
}
那么就可以通過:
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);
來獲取這個構(gòu)造函數(shù)渠概。
完整代碼:
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
//獲取構(gòu)造函數(shù)
Constructor constructor = c.getDeclaredConstructor(String.class);
constructor.setAccessible(true);//設置是否允許訪問,因為該構(gòu)造器是private的嫂拴,所以要手動設置允許訪問播揪,如果構(gòu)造器是public的就不需要這行了。
constructor.newInstance("tengj");
} catch (Exception e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果:
tengj
注意:Class的newInstance方法筒狠,只能創(chuàng)建只包含無參數(shù)的構(gòu)造函數(shù)的類猪狈,如果某類只有帶參數(shù)的構(gòu)造函數(shù),那么就要使用另外一種方式:fromClass.getDeclaredConstructor(String.class).newInstance("tengj");
獲取所有的構(gòu)造函數(shù)辩恼,可以通過以下步驟實現(xiàn):
1.獲取該類的所有構(gòu)造函數(shù)雇庙,放在一個數(shù)組中:
Constructor[] constructors = c.getDeclaredConstructors();
2.遍歷構(gòu)造函數(shù)數(shù)組谓形,獲得某個構(gòu)造函數(shù)constructor:
for (Constructor constructor : constructors)
完整代碼:
public class ReflectDemo {
public static void main(String[] args){
Constructor[] constructors = c.getDeclaredConstructors();
for(Constructor constructor:constructors){
System.out.println(constructor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果:
public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)
通過反射了解集合泛型的本質(zhì)
首先下結(jié)論:
Java中集合的泛型,是防止錯誤輸入的疆前,只在編譯階段有效寒跳,繞過編譯到了運行期就無效了。
下面通過一個實例來驗證:
public class GenericEssence {
public static void main(String[] args) {
List list1 = new ArrayList(); // 沒有泛型
List<String> list2 = new ArrayList<String>(); // 有泛型
/*
* 1.首先觀察正常添加元素方式竹椒,在編譯器檢查泛型童太,
* 這個時候如果list2添加int類型會報錯
*/
list2.add("hello");
// list2.add(20); // 報錯!list2有泛型限制胸完,只能添加String书释,添加int報錯
System.out.println("list2的長度是:" + list2.size()); // 此時list2長度為1
/*
* 2.然后通過反射添加元素方式,在運行期動態(tài)加載類赊窥,首先得到list1和list2
* 的類類型相同爆惧,然后再通過方法反射繞過編譯器來調(diào)用add方法,看能否插入int
* 型的元素
*/
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 結(jié)果:true锨能,說明類類型完全相同
// 驗證:我們可以通過方法的反射來給list2添加元素扯再,這樣可以繞過編譯檢查
try {
Method m = c2.getMethod("add", Object.class); // 通過方法反射得到add方法
m.invoke(list2, 20); // 給list2添加一個int型的,上面顯示在編譯器是會報錯的
System.out.println("list2的長度是:" + list2.size()); // 結(jié)果:2腹侣,說明list2長度增加了叔收,并沒有泛型檢查
} catch (Exception e) {
e.printStackTrace();
}
/*
* 綜上可以看出,在編譯器的時候傲隶,泛型會限制集合內(nèi)元素類型保持一致饺律,但是編譯器結(jié)束進入
* 運行期以后,泛型就不再起作用了跺株,即使是不同類型的元素也可以插入集合复濒。
*/
}
}
執(zhí)行結(jié)果:
list2的長度是:1
true
list2的長度是:2
思維導圖
最后給出一個別人整理的思維導圖: