在運行狀態中,對於任意一個類,都可以獲取到這個類的全部屬性和方法,對於任意一個對象,都可以調用它的任意一個方法和屬性(包括私有的方法和屬性),這種動態獲取的信息以及動態調用對象的方法的功能就稱爲java語言的反射機制。通俗點講,經過反射,該類對咱們來講是徹底透明的,想要獲取任何東西均可以。java
想要使用反射機制,就必需要先獲取到該類的字節碼文件對象(.class),經過字節碼文件對象,就可以經過該類中的方法獲取到咱們想要的全部信息(方法,屬性,類名,父類名,實現的全部接口等等),每個類對應着一個字節碼文件也就對應着一個Class類型的對象,也就是字節碼文件對象。數組
JAVA反射機制是在運行狀態中,
對於任意一個類,都可以知道這個類的全部屬性和方法(動態獲取的信息);
對於任意一個對象,都可以調用它的任意一個方法和屬性(動態調用對象的方法);
這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
簡言之:經過字節碼文件對象,去使用該文件中的成員變量、構造方法、成員方法。ide
獲取字節碼文件對象的三種方式。this
一、Class class1 = Class.forName("全限定類名"); //經過Class類中的靜態方法forName,直接獲取到一個類的字節碼文件對象,此時該類仍是源文件階段,並無變爲字節碼文件。加密
二、Class class2 = Person.class; //當類被加載成.class文件時,此時Person類變成了.class,在獲取該字節碼文件對象,也就是獲取本身, 該類處於字節碼階段。spa
三、Person p = new Person();對象
Class class3 = p.getClass(); //經過類的實例獲取該類的字節碼文件對象,該類處於建立對象階段 blog
第一種和後兩種的區別
後兩種你必須明確Person類型。
第一種須要這種類型的字符串就行(開發中用)。
這種擴展更強,不須要知道具體的類,只提供字符串,按照配置文件加載就能夠了。
反射的特色:
1.Class類中的靜態方法forName()傳入的字符串未來會作成配置信息文件,因此之後你不知道程序運行的是誰(是哪一個類)。
2.反射是不會看到類的任何信息的。即經過構造方法對象Constructor、成員方法對象Method,調用他們的方法返回值都是Object類型。
(由於任何類型均可以用Object類型接收,基本數據類型會自動裝箱爲引用數據類型)。
3.反射能夠訪問私有的東西(前提是class文件未被加密)。接口
三種獲取字節碼文件對應的Class類型的對象的方式內存
要想解剖一個類,必須先要獲取到該類的字節碼文件對象。
而解剖使用的就是Class類中的方法,因此先要獲取到每個字節碼文件對應的Class類型的對象。
.class文件 --> Class類
成員變量 --> Field類
構造方法 --> Constructor類
成員方法 --> Method類
有了字節碼文件對象才能得到類中全部的信息,咱們在使用反射獲取信息時,也要考慮使用上面哪一種方式獲取字節碼對象合理,視不一樣狀況而定。下面介紹Class類的功能。
實例一:
package com.my.fanshe; 2 3 public class Person { 4 private String name; 5 int age; 6 public String address; 7 8 public Person() { 9 } 10 11 private Person(String name) { 12 this.name = name; 13 } 14 15 Person(String name, int age) { 16 this.name = name; 17 this.age = age; 18 } 19 20 public Person(String name, int age, String address) { 21 this.name = name; 22 this.age = age; 23 this.address = address; 24 } 25 26 public void show() { 27 System.out.println("show"); 28 } 29 30 public void method(String s) { 31 System.out.println("method " + s); 32 } 33 34 public String getString(String s, int i) { 35 return s + "---" + i; 36 } 37 38 private void function() { 39 System.out.println("function"); 40 } 41 42 @Override 43 public String toString() { 44 return "Person [name=" + name + ", age=" + age + ", address=" + address + "]"; 45 } 46 47 }
package com.my.fanshe;
/* 4 * 反射:就是經過class文件對象,去使用該文件中的成員變量,構造方法,成員方法。 5 * 6 * Person p = new Person(); 7 * p.使用; 8 * 9 * 要想這樣使用,首先你必須獲得class文件對象,其實也就是獲得Class類的對象。 10 * .class文件 --> Class類 11 * 成員變量 --> Field類 12 * 構造方法 --> Constructor類 13 * 成員方法 --> Method類 14 * 15 * 獲取class文件對象的方式: 16 * A:Object類的getClass()方法 17 * B:數據類型的靜態屬性class(任意數據類型都具有一個class靜態屬性) 18 * C:Class類中的靜態方法(將類名做爲字符串傳遞給Class類中的靜態方法forName) 19 * public static Class forName(String className) 20 * 21 * 通常咱們到底使用誰呢? 22 * A:本身玩 任選一種,第二種比較方便 23 * B:開發時 第三種 24 * 爲何呢?由於第三種是一個字符串,而不是一個具體的類名。這樣咱們就能夠把這樣的字符串配置到配置文件中。 25 */ 26 public class ReflectDemo { 27 public static void main(String[] args) throws ClassNotFoundException { 28 // 方式A 29 Person p = new Person(); 30 Class c = p.getClass(); 31 32 Person p2 = new Person(); 33 Class c2 = p2.getClass(); 34 35 System.out.println(p == p2); // false 36 System.out.println(c == c2); // true 37 38 // 方式B 39 Class c3 = Person.class; 40 // int.class; 41 // String.class; 42 System.out.println(c == c3); // true 43 44 // 方式C 45 // ClassNotFoundException 須要類的全路徑(帶包名的路徑) 46 Class c4 = Class.forName("com.my.fanshe.Person"); 47 System.out.println(c == c4); // true 48 } 49 }
實例二:
package com.my.fanshe; public interface JieKou { public void print(); }
package com.my.fanshe; public class A implements JieKou { private String name; private int age; public A(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } @Override public void print() { System.out.println("實現A接口方法"); } @Override public String toString() { return "A{" + "name='" + name + '\'' + ", age=" + age + '}'; } public int getAge() { return age; } }
package com.my.fanshe; public class B implements JieKou { @Override public void print() { System.out.println("實現B接口方法"); } }
package com.my.fanshe; public class C { public void show(){ System.out.println("實現C方法"); } }
package com.my.fanshe; public class FansheDemo { public static void main(String[] args) throws Exception{ //方法一:經過對象得到Class類類型(靜態加載) A a = new A("張三",20); Class c = a.getClass(); System.out.println("Class類類型爲:"+c); //方法二:經過類名得到Class類類型 Class c1 = B.class; System.out.println("Class類類型爲:"+c1); //方法三:經過動態加載 Class c2 = Class.forName("com.my.fanshe.C"); System.out.println("Class類類型爲:"+c2); } }
運行結果: Class類類型爲:class com.my.fanshe.A Class類類型爲:class com.my.fanshe.B Class類類型爲:class com.my.fanshe.C
package com.my.fanshe; public interface JieKou { public void print(); }
package com.my.fanshe; public class A implements JieKou { public String name; private int age; public A(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } @Override public void print() { System.out.println("實現A接口方法"); } @Override public String toString() { return "A{" + "name='" + name + '\'' + ", age=" + age + '}'; } public int getAge() { return age; } }
package com.my.fanshe; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class FansheDemo { public static void main(String[] args) throws Exception{ //動態加載類,Class是表示當前類的類類型 //獲取字節碼文件 Class c = Class.forName("com.my.fanshe.A"); // 經過Class類類型得到類的實例 Constructor constructor = c.getConstructor(String.class,int.class); A a = (A)constructor.newInstance("張三",20); //獲取公共屬性的成員變量 Field f = c.getField("name"); System.out.println(f.get(a)); //獲取私有屬性的成員變量 Field f1 = c.getDeclaredField("age"); System.out.println(f1.getType()+" "+f1.getName()); //打開權限 f1.setAccessible(true); System.out.println(f1.get(a)); //獲取方法並執行 Method method = c.getMethod("getName"); System.out.println(method.invoke(a)); Method method1 = c.getMethod("getAge"); System.out.println(method1.invoke(a)); Method method2 = c.getMethod("print"); method2.invoke(a); Method method3 = c.getMethod("toString"); System.out.println(method3.invoke(a)); } }
運行結果: 張三 int age 20 張三 20 實現A接口方法 A{name='張三', age=20}
三、反射思惟導圖
1:反射(理解)
(1)類的加載
當程序要使用某個類時,若是該類還未被加載到內存中,則系統會經過加載、鏈接、初始化三步來實現對這個類進行初始化。
加載
就是指將class文件讀入內存,併爲之建立一個Class對象。
任何類被使用時系統都會創建一個Class對象。
鏈接
驗證:是否有正確的內部結構,並和其餘類協調一致。
準備:負責爲類的靜態成員分配內存,並設置默認初始化值。
解析:將類的二進制數據中的符號引用替換爲直接引用。
初始化
就是咱們之前講過的初始化步驟。
注意:Object類的方法:
public final Class getClass() 返回對象的字節碼文件對象
Class類的方法:
public String getName() 以 String 的形式返回此 Class 對象所表示的實體名稱。(實體包括:類、接口、數組名、基本類型或 void)
即:能夠經過Class類中的一個方法,獲取對象的真實類的全名稱。
(2)類的初始化時機 1.建立類的實例時。
2.訪問類的靜態變量,或者爲靜態變量賦值時。
3.調用類的靜態方法時。
4.使用反射方式來強制建立某個類或接口對應的java.lang.Class對象時。
5.初始化某個類的子類時。
6.直接使用java.exe命令來運行某個主類時。
(3)類加載器
負責將.class文件加載到內存中,併爲之生成對應的Class對象。
雖然咱們不須要關心類加載機制,可是瞭解這個機制咱們就能更好的理解程序的運行。
(4)類加載器的組成
Bootstrap ClassLoader 根類加載器
Extension ClassLoader 擴展類加載器
Sysetm ClassLoader 系統類加載器
Bootstrap ClassLoader 根類加載器
也被稱爲引導類加載器,負責Java核心類的加載。
好比System類,String類等。在JDK中JRE的lib目錄下rt.jar文件中(JDK8之前版本中的位置,JDK9/10位置變化了)。
Extension ClassLoader 擴展類加載器
負責JRE的擴展目錄中jar包的加載。
在JDK中JRE的lib目錄下ext目錄。
Sysetm ClassLoader 系統類加載器
負責在JVM啓動時加載來自java命令的class文件,以及classpath環境變量所指定的jar包和類路徑。
通常咱們本身寫的類經過系統類加載器來加載的。