Java反射機制定義
Java反射機制是指在運作狀态中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動态擷取的資訊以及動态調用對象的方法的功能稱為java語言的反射機制。
用一句話總結就是反射可以實作在運作時可以知道任意一個類的屬性和方法。
反射機制的優點與缺點
為什麼要用反射機制?直接建立對象不就可以了嗎,這就涉及到了動态與靜态的概念
靜态編譯:在編譯時确定類型,綁定對象,即通過。
動态編譯:運作時确定類型,綁定對象。動态編譯最大限度發揮了java的靈活性,展現了多态的應用,有以降低類之間的藕合性。
優點
可以實作動态建立對象和編譯,展現出很大的靈活性,特别是在J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟體,不可能一次就把把它設計的很完美,當這個程式編譯後,釋出了,當發現需要更新某些功能時,我們不可能要使用者把以前的解除安裝,再重新安裝新的版本,假如這樣的話,這個軟體肯定是沒有多少人用的。采用靜态的話,需要把整個程式重新編譯一次才可以實作功能的更新,而采用反射機制的話,它就可以不用解除安裝,隻需要在運作時才動态的建立和編譯,就可以實作該功能。
缺點
對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼并且它滿足我們的要求。這類操作總是慢于隻直接執行相同的操作。
了解Class類和類類型
想要了解反射首先了解一下Class類,它是反射實作的基礎。
類是java.lang.Class類的執行個體對象,而Class是所有類的類(There is a class named Class)
對于普通的對象,我們一般都會這樣建立和表示:
Code code1 = new Code();
上面說了,所有的類都是Class的對象,那麼如何表示呢,可不可以通過如下方式呢:
Class c = new Class();
但是我們檢視Class的源碼時,是這樣寫的:
private Class(ClassLoader loader) {
classLoader = loader;
}
可以看到構造器是私有的,隻有JVM可以建立Class的對象,是以不可以像普通類一樣new一個Class對象,雖然我們不能new一個Class對象,但是卻可以通過已有的類得到一個Class對象,共有三種方式,如下:
Class c1 = Code.class;
這說明任何一個類都有一個隐含的靜态成員變量class,這種方式是通過擷取類的靜态成員變量class得到的
Classc2 = code1.getClass();
code1是Code的一個對象,這種方式是通過一個類的對象的getClass()方法獲得的
Class c3 = Class.forName("com.trigl.reflect.Code");
這種方法是Class類調用forName方法,通過一個類的全量限定名獲得
這裡,c1、c2、c3都是Class的對象,他們是完全一樣的,而且有個學名,叫做Code的類類型(class type)。
這裡就讓人奇怪了,前面不是說Code是Class的對象嗎,而c1、c2、c3也是Class的對象,那麼Code和c1、c2、c3不就一樣了嗎?為什麼還叫Code什麼類類型?這裡不要糾結于它們是否相同,隻要了解類類型是幹什麼的就好了,顧名思義,類類型就是類的類型,也就是描述一個類是什麼,都有哪些東西,是以我們可以通過類類型知道一個類的屬性和方法,并且可以調用一個類的屬性和方法,這就是反射的基礎。
舉個簡單例子代碼:
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());
}
}
執行結果:
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
Java反射相關操作
前面我們知道了怎麼擷取Class,那麼我們可以通過這個Class幹什麼呢?
總結如下:
擷取成員方法Method
擷取成員變量Field
擷取構造函數Constructor
下面來具體介紹
擷取成員方法資訊
單獨擷取某一個方法是通過Class類的以下方法獲得的:
public Method getDeclaredMethod(String name, Class>... parameterTypes) // 得到該類所有的方法,不包括父類的
public Method getMethod(String name, Class>... parameterTypes) // 得到該類所有的public方法,包括父類的
兩個參數分别是方法名和方法參數類的類類型清單。
例如類A有如下一個方法:
public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"歲");
}
現在知道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調用該方法,參數第一個為執行個體對象,後面為具體參數值
完整代碼如下:
public class Person {
private String name;
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();
}
}
}
執行結果:
我叫tengj,今年10歲
怎樣,是不是感覺很厲害,我們隻要知道這個類的路徑全稱就能玩弄它于鼓掌之間。
有時候我們想擷取類中所有成員方法的資訊,要怎麼辦。可以通過以下幾步來實作:
1.擷取所有方法的數組:
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 得到該類所有的方法,不包括父類的
或者:
Method[] methods = c.getMethods();// 得到該類所有的public方法,包括父類的
2.然後循環這個數組就得到每個方法了:
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();
}
}
}
執行結果:
getName
setName
setAge
fun
fun
getAge
這裡如果把c.getDeclaredMethods();改成c.getMethods();執行結果如下,多了很多方法,以為把Object裡面的方法也列印出來了,因為Object是所有類的父類:
getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
擷取成員變量資訊
想一想成員變量中都包括什麼:成員變量類型+成員變量名
類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象,是以我們通過java.lang.reflect.Field裡面封裝的方法來擷取這些資訊。
單獨擷取某個成員變量,通過Class類的以下方法實作:
public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變量,不包括其父類的變量
public Field getField(String name) // 獲得該類自所有的public成員變量,包括其父類變量
參數是成員變量的名字。
例如一個類A有如下成員變量:
private int n;
如果A有一個對象a,那麼就可以這樣得到其成員變量:
Class c = a.getClass();
Field field = c.getDeclaredField("n");
完整代碼如下:
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);
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果:
hello wrold
同樣,如果想要擷取所有成員變量的資訊,可以通過以下幾步
1.擷取所有成員變量的數組:
Field[] fields = c.getDeclaredFields();
2.周遊變量數組,獲得某個成員變量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();
}
}
}
執行結果:
name
age
msg
擷取構造函數
最後再想一想構造函數中都包括什麼:構造函數參數
同上,類的成構造函數也是一個對象,它是java.lang.reflect.Constructor的一個對象,是以我們通過java.lang.reflect.Constructor裡面封裝的方法來擷取這些資訊。
單獨擷取某個構造函數,通過Class類的以下方法實作:
public Constructor getDeclaredConstructor(Class>... parameterTypes) // 獲得該類所有的構造器,不包括其父類的構造器
public Constructor getConstructor(Class>... parameterTypes) // 獲得該類是以public構造器,包括父類
這個參數為構造函數參數類的類類型清單。
例如類A有如下一個構造函數:
public A(String a, int b) {
// code body
}
那麼就可以通過:
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);
來擷取這個構造函數。
完整代碼:
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
//擷取構造函數
Constructor constructor = c.getDeclaredConstructor(String.class);
constructor.setAccessible(true);//設定是否允許通路,因為該構造器是private的,是以要手動設定允許通路,如果構造器是public的就不需要這行了。
constructor.newInstance("tengj");
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果:
tengj
注意:Class的newInstance方法,隻能建立隻包含無參數的構造函數的類,如果某類隻有帶參數的構造函數,那麼就要使用另外一種方式:fromClass.getDeclaredConstructor(String.class).newInstance("tengj");
擷取所有的構造函數,可以通過以下步驟實作:
1.擷取該類的所有構造函數,放在一個數組中:
Constructor[] constructors = c.getDeclaredConstructors();
2.周遊構造函數數組,獲得某個構造函數constructor:
for (Constructor constructor : constructors)
完整代碼:
public class ReflectDemo {
public static void main(String[] args){
try{
Constructor[] constructors = c.getDeclaredConstructors();
for(Constructor constructor:constructors){
System.out.println(constructor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果:
public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)
通過反射了解集合泛型的本質
首先下結論:
Java中集合的泛型,是防止錯誤輸入的,隻在編譯階段有效,繞過編譯到了運作期就無效了。
下面通過一個執行個體來驗證:
public class GenericEssence {
public static void main(String[] args) {
List list1 = new ArrayList(); // 沒有泛型
List list2 = new ArrayList(); // 有泛型
list2.add("hello");
// list2.add(20); // 報錯!list2有泛型限制,隻能添加String,添加int報錯
System.out.println("list2的長度是:" + list2.size()); // 此時list2長度為1
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 結果:true,說明類類型完全相同
// 驗證:我們可以通過方法的反射來給list2添加元素,這樣可以繞過編譯檢查
try {
Method m = c2.getMethod("add", Object.class); // 通過方法反射得到add方法
m.invoke(list2, 20); // 給list2添加一個int型的,上面顯示在編譯器是會報錯的
System.out.println("list2的長度是:" + list2.size()); // 結果:2,說明list2長度增加了,并沒有泛型檢查
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果:
list2的長度是:1
true
list2的長度是:2