Java资深反射玩家
1.反射概述
2.獲取Class類對象的三種方法
3.Class類的常用方法
4.反射獲取構造方法并使用
5.反射獲取類的成員屬性并使用
6.反射獲取類的成員方法并使用
7.反射的優缺點
8.反射的注意事項
1.反射概述
1.反射定義:
Java反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意方法和屬性。這種動態獲取信息以及動態調>用對象方法的功能稱為Java語言的反射機制。
2.關于Class類
首先,我要給初學者或者小白定位一下對Class類的理解。常用類有String類、Math類等等,這里的Class也是一個類似于String類、Math類等等的類,和我們隨便創建的類的概念是有本質區別的,Class類位于java.lang包下!Class 類的實例表示正在運行的 Java應用程序中的類和接口。也就是jvm中有N多的實例每個類都有該Class對象。(包括基本數據類型) Class 沒有公共構造方法。Class對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的defineClass方法自動構造的。也就是這不需要我們自己去處理創建Class對象,JVM已經幫我們創建好了。
3.了解完了反射的概念我們來看看普通類的加載過程:
熟悉一下加載的時候:Class對象的由來是將class文件讀入內存,并為之創建一個Class對象。反射的本質理解就是得到Class對象后反向獲取Student對象的各種成分信息(成分信息包括成員變量、方法、構造方法、所在包、字段屬性等等),下面就以Student對象為例,圖解Student類的正常加載過程~
那么我們再看看student加載的三個階段:
對于這一張圖的解釋:
我們java類的代碼編譯后生成了字節碼文件,里邊包含三個比較重要的部分,分別對應成員變量,構造方法,和成員方法(當然一個字節碼文件可不止這些東西,比如還有類名等等,這三個只是比較重要的部分),然后這個字節碼文件存儲在硬盤上,然后當我們new student()的時候,jvm就會把字節碼文件加載到內存中,在內存里有描述我們加載進來的class文件的類對象:Class類對象,我們Class類對象把三個重要部分又分別封裝成對象,分別為field,constructor,Method對象(這里注意一個對象只能包含一個成員變量或者一個構造方法成員方法,所以這里要用到數組的形式),然后真正的student對象是通過我們的Class類對象創建出來的
反射才體現出java是如何創建對象的。當java虛擬機(JVM)加載一個class類文件時,就創建這個類的class對象,以后的對象都是由這個class對象創建的,所以同一個類的所有對象的class對象都是一個,比如A a=new A(); A b=new A(); a.class()==b.class()返回true.
2.獲取Class類對象的三種方法
1.Class.forName(“包名.類名”):將字節碼文件加載進內存(注意這個方法可以加載類),返回Class類對象,當你知道該類的全路徑名時,你可以使用該方法獲取 Class 類對象。但可能拋出ClassNotFoundException異常
2.類名.class:通過類名的屬性class獲取,這種方法只適合在編譯前就知道操作的 Class。直接通過 類名.class 的方式得到,該方法最為安全可靠,程序性能更高,這說明任何一個類都有一個隱含的靜態成員變量 class
3.對象.getClass():這個getClass是在Object里邊定義著所以每一個類里邊都會有
代碼實例:
package untl1; public class student {private int age;private String name;public student(int age,String name){this.age=age;this.name=name;}public void study(){System.out.println("我愛學習");}public static void main(String[] args)throws Exception {Class cl1=Class.forName("untl1.student");System.out.println(cl1);Class cl2=student.class;System.out.println(cl2);student stu=new student(1,"1");Class cl3=stu.getClass();System.out.println(cl3);System.out.println(cl1==cl2);System.out.println(cl1==cl3);} } 運行結果: class untl1.student class untl1.student class untl1.student true true兩個true充分證明了一個類只有一個字節碼文件對象
3.Class類的常用方法
(1)常用方法:
| getName() | 一個Class對象描述了一個特定類的屬性,Class類中最常用的方法getName以=String 的形式返回此 Class對象所表示的實體(類、接口、數組類、基本類型或 void)名稱(簡單來說就是獲得類的完整路徑名)。方法可以簡單的理解為:獲得字符串參數中指定的類,并初始化該類。 |
| newInstance() | 為類創建一個實例,但只能調用默認構造器(無參數構造器) |
| getClassLoader() | getClassLoader() 方法主要返回該類的類加載器。 |
| getComponentType() | getComponentType() 方法主要返回表示數組組件類型的 Class。 |
| getSuperclass() | getSuperclass() 返回表示此 Class 所表示的實體(類、接口、基本類型或 void)的超類的 Class。 |
| isArray() | isArray() 判定此 Class 對象是否表示一個數組類。 |
(2)在初始化一個類,生成一個實例的時候,newInstance()方法和new關鍵字除了一個是方法,一個是關鍵字外,最主要有什么區別?
newInstance: 弱類型。低效率。只能調用無參構造。使用的時候使用的類必須加載過
new: 強類型。相對高效。能調用任何public構造。使用的時候不需要必須加載過
這里的加載過,博主是這么理解的,當我們使用newInstance()的時候,如果類沒加載過那么就不存在這個類的Class類對象文件,既然連這個Class類對象都沒有那么怎么調用它的方法來創建對象呢,使用new的時候,我們都知道一個類的實例是必須經過Class類對象文件的,使用new的時候不需要加載類,在new的過程中再加載該類,以這種方法來創建對象就不需要提前加載
4.反射獲取構造方法并使用
這里尤其注意看看每一個方法獲取的構造方法的訪問權限是怎樣的
例子:
拿第一個舉例子:
5.反射獲取類的成員屬性并使用
這里有兩個方法是屬性對象里邊的方法:
1.void set(Object obj,Object value):為獲取的成員變量設置值
2.get(Object obj):獲取成員變量的值
具體使用下邊例子都有:
package untl1; import java.lang.reflect.Field; public class student {public int age;private String name;public student(int age,String name){this.age=age;this.name=name;}public student(){this.name="花花";this.age=100;}public student(int age){this.name="草草";this.age=age;}public void study(){System.out.println("我愛學習");}public String toString(){return "年齡:"+age+"\n"+"名字:"+name;}public static void main(String[] args)throws Exception {Class cla1=student.class;Field fie1= cla1.getField("age");student stu1=new student();fie1.set(stu1,88);Object value1=fie1.get(stu1);System.out.println(stu1);System.out.println(value1);Field fie2=cla1.getDeclaredField("name");student stu2=new student();fie2.setAccessible(true);fie2.set(stu2,"喵喵");Object value2=fie2.get(stu2);System.out.println(stu2);System.out.println(value2);Field fie3[]= cla1.getDeclaredFields();for (Field a:fie3) {System.out.println(a);}} } 運行結果: 年齡:88 名字:花花 88 年齡:100 名字:喵喵 喵喵 public int untl1.student.age private java.lang.String untl1.student.name這里有一個fie2.setAccessible(true),這個是暴力反射,我們使用getDeclaredField()雖然可以獲得私有的屬性,但是不能拿來設置和獲得該屬性的值,這是由于訪問修飾符為private,我們想要設置和獲得該屬性的值就必須要忽略訪問修飾符的安全檢查,那么使用setAccessible()方法就能解決,這里參數必須設置為true
6.反射獲取類的成員方法并使用
獲取的成員方法需要執行可以使用Method里邊的執行方法:
Object invoke(Object obj,Object ...args):執行所在對象的指定方法
例子:
package untl1; import java.lang.reflect.Method; public class student {public int age;private String name;public student(int age,String name){this.age=age;this.name=name;}public student(){this.name="花花";this.age=100;}public student(int age){this.name="草草";this.age=age;}public void study(){System.out.println("我愛學習");}public void fly(String str){System.out.println("我要飛得更高");}public String toString(){return "年齡:"+age+"\n"+"名字:"+name;}public static void main(String[] args)throws Exception {Class cla1=student.class;Method me=cla1.getMethod("study");student stu=new student();me.invoke(stu);Method me2=cla1.getMethod("fly",String.class);me2.invoke(stu,"翠花");} } 運行結果: 我愛學習 我要飛得更高7.反射的優缺點
1.反射優點:
使用反射機制,代碼可以在運行時裝配,提高了程序的靈活性和擴展性,降低耦合性,提高自適應能力。它允許程序創建和控制任何類的對象,無需硬編碼目標類
2.反射缺點:
1.性能問題:使用反射基本上是一種解釋操作,JVM無法對這些代碼進行優化,因此,反射操作的效率要比那些非反射操作低得多。反射機制主要應用在對靈活性和擴展性要求很高的系統框架上,對性能要求高的程序中不建議使用
2.安全限制:使用反射技術要求程序必須在一個沒有安全限制的環境中運行
3.內部暴露:由于反射允許代碼執行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會導致意料之外的副作用——代碼有功能上的錯誤,降低可移植性。反射代碼破壞了抽象性,因此當平臺發生改變的時候,代碼的行為就有可能也隨著變化。
8.反射的注意事項
1.注意方法中帶Declared的不考慮訪問權限
2.在Constructor對象里的newInstance()方法,返回值需要強制類型轉換,否則不能直接就等于此字節碼文件對應的類的引用
3.在反射中傳遞參數時要在參數后邊加上.class才行
4.暴力反射是在三個對象(Constructor,Field,Method)中都能用,只要涉及獲取或者修改私有的成員屬性
5.Method里邊的getMethod方法如果有沒參數就直接在參數列表里邊以字符串的形式傳遞方法的名字,如果有參數,不僅要傳遞方法的名字,還要傳遞方法的參數
6.Method中invoke方法需要傳遞一個實例化對象,然后如果獲得的方法有參數,海要在invoke里邊加上方法的參數
總結
以上是生活随笔為你收集整理的Java资深反射玩家的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java方法的引用(打造Lambda表达
- 下一篇: Java注解原来如此通俗易懂