Java反射基礎

Java反射機制定義

  Java反射機制是指在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
用一句話總結就是反射能夠實如今運行時能夠知道任意一個類的屬性和方法。java

反射機制的優勢與缺點

爲何要用反射機制?直接建立對象不就能夠了嗎,這就涉及到了動態與靜態的概念python

  • 靜態編譯:在編譯時肯定類型,綁定對象,即經過。數組

  • 動態編譯:運行時肯定類型,綁定對象。動態編譯最大限度發揮了java的靈活性,體現了多態的應用,有以下降類之間的藕合性。bash

  • 優勢
    能夠實現動態建立對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。好比,一個大型的軟件,不可能一次就把把它設計的很完美,當這個程序編譯後,發佈了,當發現須要更新某些功能時,咱們不可能要用戶把之前的卸載,再從新安裝新的版本,假如這樣的話,這個軟件確定是沒有多少人用的。採用靜態的話,須要把整個程序從新編譯一次才能夠實現功能的更新,而採用反射機制的話,它就能夠不用卸載,只須要在運行時才動態的建立和編譯,就能夠實現該功能。函數

  • 缺點
    對性能有影響。使用反射基本上是一種解釋操做,咱們能夠告訴JVM,咱們但願作什麼而且它知足咱們的要求。這類操做老是慢於只直接執行相同的操做。性能

理解Class類和類類型

想要了解反射首先理解一下Class類,它是反射實現的基礎。
類是java.lang.Class類的實例對象,而Class是全部類的類(There is a class named Class)
對於普通的對象,咱們通常都會這樣建立和表示:this

Code code1 = new Code(); 

上面說了,全部的類都是Class的對象,那麼如何表示呢,可不能夠經過以下方式呢:spa

Class c = new Class(); 

可是咱們查看Class的源碼時,是這樣寫的:設計

private Class(ClassLoader loader) { classLoader = loader; } 

能夠看到構造器是私有的,只有JVM能夠建立Class的對象,所以不能夠像普通類同樣new一個Class對象,雖然咱們不能new一個Class對象,可是卻能夠經過已有的類獲得一個Class對象,共有三種方式,以下:code

Class c1 = Code.class; 這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是經過獲取類的靜態成員變量class獲得的 Class c2 = code1.getClass(); code1是Code的一個對象,這種方式是經過一個類的對象的getClass()方法得到的 Class c3 = Class.forName("com.trigl.reflect.Code"); 這種方法是Class類調用forName方法,經過一個類的全量限定名得到 

這裏,c一、c二、c3都是Class的對象,他們是徹底同樣的,並且有個學名,叫作Code的類類型(class type)。
這裏就讓人奇怪了,前面不是說Code是Class的對象嗎,而c一、c二、c3也是Class的對象,那麼Code和c一、c二、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<T> getDeclaredConstructor(Class<?>... parameterTypes) // 得到該類全部的構造器,不包括其父類的構造器 public Constructor<T> 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<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.而後經過反射添加元素方式,在運行期動態加載類,首先獲得list1和list2 * 的類類型相同,而後再經過方法反射繞過編譯器來調用add方法,看可否插入int * 型的元素 */ 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
相關文章
相關標籤/搜索