Java基礎之深入理解Class物件與反射機制
深入理解Class物件
RRIT及Class物件的概念
RRIT(Run-Time Type Identification)執行時型別識別。在《Thinking in Java》一書第十四章中有提到,它的功能是在執行時識別物件的型別和類資訊。有兩種主要方式:“傳統的”RTTI(它假定我們在編譯時已經知道所有型別)和“反射”機制(它允許我們在執行時發現和使用類資訊)。
類是程式的一部分,每個類都有一個類物件。換句話說,無論何時編寫和編譯新類,都會生成一個Class物件(更恰當地說,儲存在相同名稱的A.class檔案中)。當第一次使用所有類時,它們都被動態地載入到JVM中。例如,我們編寫了一個Test類並編譯它來生成Test。班級。此時,Test類的Class物件儲存在類檔案中。當我們新建一個物件或引用一個靜態成員變數時,Java虛擬機器(JVM)中的類載入器子系統將相應的類物件載入到JVM中,然後JVM從這個型別的資訊中建立我們需要的類物件,或者提供靜態變數的參考值。應當注意,無論建立了多少例項物件,手動編寫的每個類類類在JVM中都只有一個Class物件,也就是說,每個類在記憶體中都具有並且只有一個對應的Class物件。
1 Test t1 = new Test(); 2 Test t2 = new Test(); 3 Test t3 = new Test();
如上所示,實際上JVM記憶體中只存有一個Test的Class物件。
Class類,類類也是Java中存在的一個真實類。JDK的Lang軟體包。類類的例項表示Java應用程式執行時的類列舉或介面和註釋(每個Java類執行時被表示為JVM中的類物件),類物件可以通過類名來獲得。類,型別。getClass(),Class.forName(“類名”)。陣列還對映到一個類物件,該類物件由具有相同元素型別和維度的所有陣列共享。基本型別布林、位元組、char、.、int、long、float、double和關鍵詞void也表示為類物件。
1 ublic final class Class<T> implements java.io.Serializable, 2 GenericDeclaration, 3 Type, 4 AnnotatedElement { 5 private static final int ANNOTATION= 0x00002000; 6 private static finalint ENUM = 0x00004000; 7 private static final int SYNTHETIC = 0x00001000; 8 9 private static native void registerNatives(); 10 static { 11 registerNatives(); 12 } 13 14 /* 15 * Private constructor. Only the Java Virtual Machine creates Class objects. //私有構造器,只有JVM才能呼叫建立Class物件 16 * This constructor is not used and prevents the default constructor being 17 * generated. 18 */ 19 private Class(ClassLoader loader) { 20 // Initialize final field for classLoader. The initialization value of non-null 21 // prevents future JIT optimizations from assuming this final field is null. 22 classLoader = loader; 23 }
到這我們也就可以得出以下幾點資訊:
-
Class類也是類的一種,與class關鍵字是不一樣的。
-
手動編寫的類被編譯後會產生一個Class物件,其表示的是建立的類的型別資訊,而且這個Class物件儲存在同名.class的檔案中(位元組碼檔案)
-
每個通過關鍵字class標識的類,在記憶體中有且只有一個與之對應的Class物件來描述其型別資訊,無論建立多少個例項物件,其依據的都是用一個Class物件。
-
Class類只存私有建構函式,因此對應Class物件只能有JVM建立和載入
-
Class類的物件作用是執行時提供或獲得某個物件的型別資訊,這點對於反射技術很重要(關於反射稍後分析)。
Class物件的載入及獲取
Class物件的載入
正如我們前面提到的,類物件是由JVM載入的,所以什麼時候載入呢?實際上,所有類在第一次使用時都動態地載入到JVM中。當程式建立對該類的第一個靜態成員引用時,它載入使用的類(實際載入該類的位元組碼檔案)。注意,使用新操作符建立類的新例項物件也被視為對類的靜態成員(建構函式也是一個類)的引用。看來Java程式在開始執行之前沒有完全載入到記憶體中,而且它們的所有部分都按需載入。因此,當使用這個類時,類載入器首先檢查這個類的Class物件是否已經被載入(類的例項物件是根據Class物件中的型別資訊建立的)。如果未載入,則預設的類載入Class物件將以相同的名稱儲存。編譯後的類檔案。當該類的位元組碼檔案被載入時,它們必須接受相關的驗證,以確保它們不被破壞,並且不包含壞的Java程式碼(這是Java的安全機制檢測)。在沒有問題之後,它們將被動態地載入到記憶體中,這相當於Cl。ass物件被載入到記憶體中(畢竟,類位元組碼檔案儲存Cl ass物件),並且還可以用於建立類的所有例項物件。
類載入的過程 :
1. 載入
在載入階段,虛擬機器需要完成3件事:
(1)通過一個類的全限定名(org/fenixsoft/clazz/TestClass)獲取定義此類的二進位制位元組流(.class檔案);
(2)將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構;
(3)在記憶體中生成一個代表這個類的 java.lang.Class 物件,作為方法區這個類的各種資料的訪問入口;
2. 驗證
驗證階段是非常重要的,這個階段是否嚴謹,直接決定了Java虛擬機器是否能承受惡意程式碼的攻擊,從執行效能的角度上講,驗證階段的工作量在虛擬機器的類載入子系統中又佔了相當大的一部分。驗證階段大致上完成下面4個階段的驗證動作:
(1)檔案格式驗證
驗證位元組流是否符合Class檔案格式的規範,並且能被當前版本的虛擬機器處理;
這階段的驗證是基於二進位制位元組流進行的,只有通過了這個階段的驗證,位元組流才會進入記憶體的方法區進行儲存,所以後面的3個驗證階段全部是基於方法區的儲存結構進行的,不會再直接操作位元組流。
(2)元資料驗證
對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合Java語言規範的要求,保證不存在不符合Java語言規範的元資料資訊;
(3)位元組碼驗證
通過資料流和控制流分析,確定程式是語義是合法的、符合邏輯的,保證被校驗的方法在執行時不會做出危害虛擬機器安全的事件;
(4)符號引用驗證
可以看作是對類自身以外(常量池中各種符號引用)的資訊進行匹配性校驗,確保解析動作能正常執行;
3. 準備
準備階段是正式為類變數分配記憶體並設定類變數初始值階段,這些變數所使用的記憶體都將在方法區中進行分配。這裡進行記憶體分配僅僅是類變數(被static修飾的變數),而不包括例項變數,例項變數將在物件例項化時隨著物件一起分配在Java堆中;
4. 解析
解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或介面、欄位、類方法、方法型別、方法控制代碼和呼叫點限定符7類符號引用進行;
5. 初始化
初始化階段才真正開始執行類中定義的Java程式程式碼(或者說是位元組碼)。初始化是如何被觸發的:
(1)遇到new、getstatic、putstatic或involestatic這4條指令時;
(2)使用 java.lang.reflect 包的方法對類進行反射呼叫的時候;
(3)初始化一個類時,如果父類還沒被初始化,則先觸發父類的初始化;
(4)虛擬機器啟動時,使用者需要指定一個要執行的主類 (包含main()方法的那個類),虛擬機器會先初始化這個主類;
(5)如果一個 java.lang.invoke.MethodHandle 例項最後解析的結果是 REF_getStatic、REF_putStatic、REF_invokeStatic的方法控制代碼,若控制代碼所對應的類沒有進行過初始化,則將它初始化;
上文源自《深入理解java虛擬機器》一書,大家可以去讀一下,這本書基本上是java程式猿學習必讀之一了。在此就不深入展開了,因為這又是另一個JVM領域了。 以後如果寫了該方面的文章,會貼到這裡。
Class物件的獲取
Class物件的獲取主要有3種:
- 通過例項getClass()方法獲取
- Class.forName方法獲取
- 類字面常量獲取
通過例項getClass()方法獲取
1 Test t1 = new Test(); 2 Class clazz=test.getClass(); 3 System.out.println("clazz:"+clazz.getName());
getClass()是從頂級類Object繼承而來的,它將返回表示該物件的實際型別的Class物件引用。
Class.forName方法獲取
forName方法是Class類的靜態成員方法,記住所有Class物件都源自這個Class類,因此Class類中定義的方法適用於所有Class物件。這裡,通過forName方法,我們可以獲得Test類的相應Class物件引用。注意,當呼叫forName方法時,您需要捕獲一個名為ClassNotFoundException的異常,因為forName方法無法檢測編譯器中與其傳遞的字串(是否有.class檔案)對應的類的存在,並且只能在程式的執行時進行檢查。如果不存在,將引發ClassNotFoundException異常。
使用forName方式會觸發類的初始化,與之相比的是使用類字面常量獲取
類字面常量獲取
1 //字面常量的方式獲取Class物件 2 Class clazz = Test.class;
這不僅更簡單,而且更安全,因為它是在編譯時檢查的(因此不需要放在try語句塊中)。而且它消除了對forName()方法的呼叫,因此也更有效。注意,當您使用“.“類”建立對Class物件的引用,Class物件不會自動初始化。注意,當您使用“.“類”建立對Classs物件的引用,Class物件不會自動初始化。使用該類的準備實際上包括三個步驟:
- 載入,這是由類載入器執行的,該步驟將查詢位元組碼(通常在classpath所指定的路徑中查詢,但這並非是必需的),並從這些位元組碼中建立一個Class物件。
- 連結。在連結階段將驗證類中的位元組碼,為靜態域分佈儲存空間,並且如果必需的話,將解析這個類建立的對其他類的所有引用。
- 初始化。如果該類具有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。
1 class Initable{ 2 static final int staticFinal = 47; 3 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000); 4 static { 5 System.out.ptintln("Initializing Initable"); 6 } 7 } 8 9 class Initable2 { 10 static int staticNonFinal = 147; 11 static { 12 System.out.println("Initializing Initable2"); 13 } 14 } 15 16 class Initable3 { 17 static int staticNonFinal = 74; 18 static { 19 System.out.println("Initializing Initable3"); 20 } 21 } 22 23 public class ClassInitialization { 24 public static Random rand = new Random(47); 25 public static void main(String[] args) throws Exception { 26 Class initable = Initable.class; 27 System.out.println("After creating Initable ref"); 28 System.out.println(Initable.staticFinal); 29 System.out.println(Initable.staticFinal2); 30 System.out.println(Initable2.staticNonFinal); 31 Clas initable3 = Class.forName("Initable3"); 32 System.out.println("After creating Initable3 ref"); 33 System.out.println(Initable3.staticNonFinal); 34 } 35 } 36 37 /* output 38 After creating Initable ref 39 47 40 Initializing Initable 41 258 42 Initializing Initable2 43 147 44 Initializing Initable3 45 After creating Initable ref 46 74
如果一個static final值是編譯器常量
,就像Initable.staticFinal那樣,那麼這個值不需要對Initable類進行初始化就可以被讀取。但是,如果只是將一個域設定為static和final的,還不足以確保這種行為,例如,對Initable.staticFinal2的訪問將強制進行類的初始化,因為它不是一個編譯期常量。
如果靜態域不是最終的,則總是需要在讀取之前進行連結(為域分配儲存空間)和初始化(初始化儲存空間),如訪問Initable2所示。靜態非決賽。從輸出結果可以看出,通過文字常數獲取獲得的Initable類的Class物件不觸發Initable類的初始化,這也驗證了前面的分析。同時,還發現不可逆的。staticFinal變數不觸發初始化,因為staticFinal在編譯時屬於靜態常數,並且在編譯階段通過常數傳播優化。公式將Initable類的常量staticFinal儲存在一個名為NotInitialization類的常量池中。將來,對常量staticFinal of Initable類的引用實際上被轉換成對其自己的常量NotInitialization類池的引用。因此,在編譯之後,對編譯時間常數的引用將在NotInitialization類的常量池中獲得,這也是引用編譯時間。靜態常數不觸發Initable類初始化的一個重要原因。然而,不適宜的。然後呼叫staticFinal2變數來觸發Initable類的初始化。注意,儘管staticFinal2由static和final修改,但它的值在編譯時無法確定。因此,staticFinal2不是編譯時間常數,在使用此變數之前,必須初始化Initable類。Initable2和Initable3是靜態成員變數,而不是編譯時常數,引用觸發初始化。至於forName方法獲取Class物件,初始化被繫結到.,前面已經對此進行了分析。
instanceof與Class的等價性
關於instanceof 關鍵字,它返回一個boolean型別的值,意在告訴我們物件是不是某個特定的型別例項。如下,在強制轉換前利用instanceof檢測obj是不是Animal型別的例項物件,如果返回true再進行型別轉換,這樣可以避免丟擲型別轉換的異常(ClassCastException)
而isInstance方法則是Class類中的一個Native方法,也是用於判斷物件型別的,看個簡單例子:
事實上instanceOf 與isInstance方法產生的結果是相同的。
1 class A {} 2 3 class B extends A {} 4 5 public class C { 6 static void test(Object x) { 7 print("Testing x of type " + x.getClass()); 8 print("x instanceof A " + (x instanceof A)); 9 print("x instanceof B "+ (x instanceof B)); 10 print("A.isInstance(x) "+ A.class.isInstance(x)); 11 print("B.isInstance(x) " + 12 B.class.isInstance(x)); 13 print("x.getClass() == A.class " + 14 (x.getClass() == A.class)); 15 print("x.getClass() == B.class " + 16 (x.getClass() == B.class)); 17 print("x.getClass().equals(A.class)) "+ 18 (x.getClass().equals(A.class))); 19 print("x.getClass().equals(B.class)) " + 20 (x.getClass().equals(B.class))); 21 } 22 public static void main(String[] args) { 23 test(new A()); 24 test(new B()); 25 } 26 } 27 28 /* output 29 Testing x of type class com.zejian.A 30 x instanceof A true 31 x instanceof B false //父類不一定是子類的某個型別 32 A.isInstance(x) true 33 B.isInstance(x) false 34 x.getClass() == A.class true 35 x.getClass() == B.class false 36 x.getClass().equals(A.class)) true 37 x.getClass().equals(B.class)) false 38 --------------------------------------------- 39 Testing x of type class com.zejian.B 40 x instanceof A true 41 x instanceof B true 42 A.isInstance(x) true 43 B.isInstance(x) true 44 x.getClass() == A.class false 45 x.getClass() == B.class true 46 x.getClass().equals(A.class)) false 47 x.getClass().equals(B.class)) true
反射
反射機制是在執行狀態下可以知道任何類的所有屬性和方法,並且可以呼叫任何物件的任何方法和屬性。獲得的動態資訊和物件的動態呼叫方法的功能被稱為Java語言的反射機制。反射技術一直是爪哇的一個亮點,它也是大多數框架(如Spring/MybATIS等)要實現的骨幹。在Java中,類類和Java。反射類庫共同為反射技術提供了充分的支援。在反射包中,我們通常使用建構函式類來構造由類物件表示的類。通過使用Constructor類,我們可以在執行時動態建立物件以及由Class物件表示的Field類。通過使用Constructor類,我們可以在執行時動態修改成員變數(包括私有)和由Class物件表示的方法類的屬性值。方法,通過該方法可以動態呼叫物件的方法(包括私有類),下面將分別解釋這些重要的類。
Constructor類及其用法
Constructor類存在於反射包(java.lang.reflect)中,反映的是Class 物件所表示的類的構造方法。獲取Constructor物件是通過Class類中的方法獲取的,Class類與Constructor相關的主要方法如下:
下面看一個簡單例子來了解Constructor物件的使用:
1 public class ConstructionTest implements Serializable { 2 public static void main(String[] args) throws Exception { 3 4 Class<?> clazz = null; 5 6 //獲取Class物件的引用 7 clazz = Class.forName("com.example.javabase.User"); 8 9 //第一種方法,例項化預設構造方法,User必須無參建構函式,否則將拋異常 10 User user = (User) clazz.newInstance(); 11 user.setAge(20); 12 user.setName("Jack"); 13 System.out.println(user); 14 15 System.out.println("--------------------------------------------"); 16 17 //獲取帶String引數的public建構函式 18 Constructor cs1 =clazz.getConstructor(String.class); 19 //建立User 20 User user1= (User) cs1.newInstance("hiway"); 21 user1.setAge(22); 22 System.out.println("user1:"+user1.toString()); 23 24 System.out.println("--------------------------------------------"); 25 26 //取得指定帶int和String引數建構函式,該方法是私有構造private 27 Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class); 28 //由於是private必須設定可訪問 29 cs2.setAccessible(true); 30 //建立user物件 31 User user2= (User) cs2.newInstance(25,"hiway2"); 32 System.out.println("user2:"+user2.toString()); 33 34 System.out.println("--------------------------------------------"); 35 36 //獲取所有構造包含private 37 Constructor<?> cons[] = clazz.getDeclaredConstructors(); 38 // 檢視每個構造方法需要的引數 39 for (int i = 0; i < cons.length; i++) { 40 //獲取建構函式引數型別 41 Class<?> clazzs[] = cons[i].getParameterTypes(); 42 System.out.println("建構函式["+i+"]:"+cons[i].toString() ); 43 System.out.print("引數型別["+i+"]:("); 44 for (int j = 0; j < clazzs.length; j++) { 45 if (j == clazzs.length - 1) 46 System.out.print(clazzs[j].getName()); 47 else 48 System.out.print(clazzs[j].getName() + ","); 49 } 50 System.out.println(")"); 51 } 52 } 53 } 54 55 56 class User { 57 private int age; 58 private String name; 59 public User() { 60 super(); 61 } 62 public User(String name) { 63 super(); 64 this.name = name; 65 } 66 67 /** 68 * 私有構造 69 * @param age 70 * @param name 71 */ 72 private User(int age, String name) { 73 super(); 74 this.age = age; 75 this.name = name; 76 } 77 78 public int getAge() { 79 return age; 80 } 81 82 public void setAge(int age) { 83 this.age = age; 84 } 85 86 public String getName() { 87 return name; 88 } 89 90 public void setName(String name) { 91 this.name = name; 92 } 93 94 @Override 95 public String toString() { 96 return "User{" + 97 "age=" + age + 98 ", name='" + name + '\'' + 99 '}'; 100 } 101 } 102 103 /* output 104 User{age=20, name='Jack'} 105 -------------------------------------------- 106 user1:User{age=22, name='hiway'} 107 -------------------------------------------- 108 user2:User{age=25, name='hiway2'} 109 -------------------------------------------- 110 建構函式[0]:private com.example.javabase.User(int,java.lang.String) 111 引數型別[0]:(int,java.lang.String) 112 建構函式[1]:public com.example.javabase.User(java.lang.String) 113 引數型別[1]:(java.lang.String) 114 建構函式[2]:public com.example.javabase.User() 115 引數型別[2]:()
關於Constructor類本身一些常用方法如下(僅部分,其他可查API)
Field類及其用法
Field 提供有關類或介面的單個欄位的資訊,以及對它的動態訪問許可權。反射的欄位可能是一個類(靜態)欄位或例項欄位。同樣的道理,我們可以通過Class類的提供的方法來獲取代表欄位資訊的Field物件,Class類與Field物件相關方法如下:
下面的程式碼演示了上述方法的使用過程
1 public class ReflectField { 2 3 public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { 4 Class<?> clazz = Class.forName("reflect.Student"); 5 //獲取指定欄位名稱的Field類,注意欄位修飾符必須為public而且存在該欄位, 6 // 否則拋NoSuchFieldException 7 Field field = clazz.getField("age"); 8 System.out.println("field:"+field); 9 10 //獲取所有修飾符為public的欄位,包含父類欄位,注意修飾符為public才會獲取 11 Field fields[] = clazz.getFields(); 12 for (Field f:fields) { 13 System.out.println("f:"+f.getDeclaringClass()); 14 } 15 16 System.out.println("================getDeclaredFields===================="); 17 //獲取當前類所欄位(包含private欄位),注意不包含父類的欄位 18 Field fields2[] = clazz.getDeclaredFields(); 19 for (Field f:fields2) { 20 System.out.println("f2:"+f.getDeclaringClass()); 21 } 22 //獲取指定欄位名稱的Field類,可以是任意修飾符的自動,注意不包含父類的欄位 23 Field field2 = clazz.getDeclaredField("desc"); 24 System.out.println("field2:"+field2); 25 } 26 /** 27 輸出結果: 28 field:public int reflect.Person.age 29 f:public java.lang.String reflect.Student.desc 30 f:public int reflect.Person.age 31 f:public java.lang.String reflect.Person.name 32 33 ================getDeclaredFields==================== 34 f2:public java.lang.String reflect.Student.desc 35 f2:private int reflect.Student.score 36 field2:public java.lang.String reflect.Student.desc 37 */ 38 } 39 40 class Person{ 41 public int age; 42 public String name; 43 //省略set和get方法 44 } 45 46 class Student extends Person{ 47 public String desc; 48 private int score; 49 //省略set和get方法 50 }應當注意,如果我們不希望獲得其父類的欄位,則需要使用Class類的getDeclared./getDeclaredFields方法來獲得欄位。如果需要聯合地獲取父類的欄位,我們可以使用Class類的get./getFi.,但是隻能獲得由public修飾的欄位,而不能獲得父類的私有欄位。 其中的set(Object obj, Object value)方法是Field類本身的方法,用於設定欄位的值,而get(Object obj)則是獲取欄位的值,當然關於Field類還有其他常用的方法如下:
這些方法可能更常用。實際上,Field類還提供了專門用於基本資料型別的方法,例如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等。這裡沒有列出全部。您可以在需要時檢視API文件。應特別注意由final關鍵字修改的Field欄位是安全的,並且可以在執行時接受任何修改,但其實際值最終不會改變。
Method類及其用法
Method 提供關於類或介面上單獨某個方法(以及如何訪問該方法)的資訊,所反映的方法可能是類方法或例項方法(包括抽象方法)。下面是Class類獲取Method物件相關的方法:
同樣通過案例演示上述方法:
1 import java.lang.reflect.Method; 2 3 public class ReflectMethod { 4 5 6 public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { 7 8 Class clazz = Class.forName("reflect.Circle"); 9 10 //根據引數獲取public的Method,包含繼承自父類的方法 11 Method method = clazz.getMethod("draw",int.class,String.class); 12 13 System.out.println("method:"+method); 14 15 //獲取所有public的方法: 16 Method[] methods =clazz.getMethods(); 17 for (Method m:methods){ 18 System.out.println("m::"+m); 19 } 20 21 System.out.println("========================================="); 22 23 //獲取當前類的方法包含private,該方法無法獲取繼承自父類的method 24 Method method1 = clazz.getDeclaredMethod("drawCircle"); 25 System.out.println("method1::"+method1); 26 //獲取當前類的所有方法包含private,該方法無法獲取繼承自父類的method 27 Method[] methods1=clazz.getDeclaredMethods(); 28 for (Method m:methods1){ 29 System.out.println("m1::"+m); 30 } 31 } 32 33 /** 34 輸出結果: 35 method:public void reflect.Shape.draw(int,java.lang.String) 36 37 m::public int reflect.Circle.getAllCount() 38 m::public void reflect.Shape.draw() 39 m::public void reflect.Shape.draw(int,java.lang.String) 40 m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException 41 m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException 42 m::public final void java.lang.Object.wait() throws java.lang.InterruptedException 43 m::public boolean java.lang.Object.equals(java.lang.Object) 44 m::public java.lang.String java.lang.Object.toString() 45 m::public native int java.lang.Object.hashCode() 46 m::public final native java.lang.Class java.lang.Object.getClass() 47 m::public final native void java.lang.Object.notify() 48 m::public final native void java.lang.Object.notifyAll() 49 50 ========================================= 51 method1::private void reflect.Circle.drawCircle() 52 53 m1::public int reflect.Circle.getAllCount() 54 m1::private void reflect.Circle.drawCircle() 55 */ 56 } 57 58 class Shape { 59 public void draw(){ 60 System.out.println("draw"); 61 } 62 63 public void draw(int count , String name){ 64 System.out.println("draw "+ name +",count="+count); 65 } 66 67 } 68 class Circle extends Shape{ 69 70 private void drawCircle(){ 71 System.out.println("drawCircle"); 72 } 73 public int getAllCount(){ 74 return 100; 75 } 76 }在通過getMethods方法獲取Method物件時,會把父類的方法也獲取到,如上的輸出結果,把Object類的方法都打印出來了。而getDeclaredMethod/getDeclaredMethods方法都只能獲取當前類的方法。我們在使用時根據情況選擇即可。 在上述程式碼中呼叫方法,使用了Method類的invoke(Object obj,Object... args)第一個引數代表呼叫的物件,第二個引數傳遞的呼叫方法的引數。這樣就完成了類方法的動態呼叫。 getReturnType方法/getGenericReturnType方法都是獲取Method物件表示的方法的返回型別,只不過前者返回的Class型別後者返回的Type(前面已分析過),Type就是一個介面而已,在Java8中新增一個預設的方法實現,返回的就引數型別資訊 。