Java學習_反射
- 什麼是反射?
- 反射就是Reflection,Java的反射是指程式在執行期可以拿到一個物件的所有資訊。
- 反射是為了解決在執行期,對某個例項一無所知的情況下,如何呼叫其方法。
- JAVA反射機制是在執行狀態中,對於任何一個類,都能夠知道這個類的所有屬性和方法;對於任何一個物件,都能夠呼叫它的任意方法和屬性;這種動態獲取資訊以及動態呼叫物件方法的功能稱為java語言的反射機制。
- Class類
class
是由JVM在執行過程中動態載入的。JVM在第一次讀取到一種class
型別時,將其載入進記憶體。每載入一種class
,JVM就為其建立一個Class
型別的例項,並關聯起來。注意:這裡的Class
Class
的class
。public final class Class { private Class() {} }
以
String
類為例,當JVM載入String
類時,它首先讀取String.class
檔案到記憶體,然後,為String
類建立一個Class
例項並關聯起來。Class cls = new Class(String);
檢視JDK原始碼,可以發現
Class
類的構造方法是private
,只有JVM能建立Class
例項,我們自己的Java程式是無法建立Class
例項的。JVM持有的每個Class
例項都指向一個數據型別(class
或interface
Class
例項包含了該class
的所有完整資訊。┌───────────────────────────┐ │ Class Instance │──────> String ├───────────────────────────┤ │name = "java.lang.String" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Random ├───────────────────────────┤ │name = "java.util.Random" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Runnable ├───────────────────────────┤ │name = "java.lang.Runnable"│ └───────────────────────────┘
┌───────────────────────────┐ │ Class Instance │──────> String ├───────────────────────────┤ │name = "java.lang.String" │ ├───────────────────────────┤ │package = "java.lang" │ ├───────────────────────────┤ │super = "java.lang.Object" │ ├───────────────────────────┤ │interface = CharSequence...│ ├───────────────────────────┤ │field = value[],hash,... │ ├───────────────────────────┤ │method = indexOf()... │ └───────────────────────────┘
-
JVM為每個載入的
class
建立了對應的Class
例項,並在例項中儲存了該class
的所有資訊,包括類名、包名、父類、實現的介面、所有方法、欄位等,因此,如果獲取了某個Class
例項,我們就可以通過這個Class
例項獲取到該例項對應的class
的所有資訊。這種通過Class
例項獲取class
資訊的方法稱為反射(Reflection)。 - 獲取一個
class
的Class
例項,有三個方法。- 方法一:直接通過一個
class
的靜態變數class
獲取。Class cls = String.class;
-
如果有一個例項變數,可以通過該例項變數提供的
getClass()
方法獲取。String s = "Hello"; Class cls = s.getClass();
-
如果知道一個
class
的完整類名,可以通過靜態方法Class.forName()
獲取。Class cls = Class.forName("java.lang.String");
- 方法一:直接通過一個
Class
例項在JVM中是唯一的,所以,上述方法獲取的Class
例項是同一個例項。可以用==
比較兩個Class
例項。Integer n = new Integer(123); boolean b1 = n instanceof Integer; // true,因為n是Integer型別 boolean b2 = n instanceof Number; // true,因為n是Number型別的子類 boolean b3 = n.getClass() == Integer.class; // true,因為n.getClass()返回Integer.class boolean b4 = n.getClass() == Number.class; // false,因為Integer.class!=Number.class
用
instanceof
不但匹配指定型別,還匹配指定型別的子類。而用==
判斷class
例項可以精確地判斷資料型別,但不能作子型別比較。通常情況下,我們應該用instanceof
判斷資料型別,因為面向抽象程式設計的時候,我們不關心具體的子型別。只有在需要精確判斷一個型別是不是某個class
的時候,我們才使用==
判斷class
例項。- 從
Class
例項獲取獲取的基本資訊。1 public class Main { 2 public static void main(String[] args) { 3 printClassInfo("".getClass()); 4 printClassInfo(Runnable.class); 5 printClassInfo(java.time.Month.class); 6 printClassInfo(String[].class); 7 printClassInfo(int.class); 8 } 9 10 static void printClassInfo(Class cls) { 11 System.out.println("Class name: " + cls.getName()); 12 System.out.println("Simple name: " + cls.getSimpleName()); 13 if (cls.getPackage() != null) { 14 System.out.println("Package name: " + cls.getPackage().getName()); 15 } 16 System.out.println("is interface: " + cls.isInterface()); 17 System.out.println("is enum: " + cls.isEnum()); 18 System.out.println("is array: " + cls.isArray()); 19 System.out.println("is primitive: " + cls.isPrimitive()); 20 } 21 }
- 獲取到了一個
Class
例項,可以通過該Class
例項來建立對應型別的例項。// 獲取String的Class例項: Class cls = String.class; // 建立一個String例項: String s = (String) cls.newInstance();
上述程式碼相當於
new String()
。通過Class.newInstance()
可以建立類例項,它的侷限是:只能呼叫public
的無引數構造方法。帶引數的構造方法,或者非public
的構造方法都無法通過Class.newInstance()
被呼叫。 -
動態載入
-
JVM在執行Java程式的時候,並不是一次性把所有用到的class全部載入到記憶體,而是第一次需要用到class時才載入。
-
- 訪問欄位
-
Class
類提供了以下幾個方法來獲取欄位:- Field getField(name):根據欄位名獲取某個public的field(包括父類)
- Field getDeclaredField(name):根據欄位名獲取當前類的某個field(不包括父類)
- Field[] getFields():獲取所有public的field(包括父類)
- Field[] getDeclaredFields():獲取當前類的所有field(不包括父類)
1 public class Main { 2 public static void main(String[] args) throws Exception { 3 Class stdClass = Student.class; 4 // 獲取public欄位"score": 5 System.out.println(stdClass.getField("score")); 6 // 獲取繼承的public欄位"name": 7 System.out.println(stdClass.getField("name")); 8 // 獲取private欄位"grade": 9 System.out.println(stdClass.getDeclaredField("grade")); 10 } 11 } 12 13 class Student extends Person { 14 public int score; 15 private int grade; 16 } 17 18 class Person { 19 public String name; 20 }
-
一個
Field
物件包含了一個欄位的所有資訊:getName()
:返回欄位名稱,例如,"name"
;getType()
:返回欄位型別,也是一個Class
例項,例如,String.class
;getModifiers()
:返回欄位的修飾符,它是一個int
,不同的bit表示不同的含義。-
以
String
類的value
欄位為例,它的定義是:public final class String { private final byte[] value; }
用反射獲取該欄位的資訊:
Field f = String.class.getDeclaredField("value"); f.getName(); // "value" f.getType(); // class [B 表示byte[]型別 int m = f.getModifiers(); Modifier.isFinal(m); // true Modifier.isPublic(m); // false Modifier.isProtected(m); // false Modifier.isPrivate(m); // true Modifier.isStatic(m); // false
-
獲取欄位值
-
利用反射拿到欄位的一個
Field
例項只是第一步,還可以拿到一個例項對應的該欄位的值。例如,對於一個Person
例項,可以先拿到name
欄位對應的Field
,再獲取這個例項的name
欄位的值。1 import java.lang.reflect.Field; 2 3 public class Main { 4 5 public static void main(String[] args) throws Exception { 6 Object p = new Person("Xiao Ming"); 7 Class c = p.getClass(); 8 Field f = c.getDeclaredField("name");
f.setAccessible(true); 9 Object value = f.get(p); 10 System.out.println(value); // "Xiao Ming" 11 } 12 } 13 14 class Person { 15 private String name; 16 17 public Person(String name) { 18 this.name = name; 19 } 20 }Object
get(Objectobj)
返回指定物件上此 欄位
表示的欄位的值。
-
-
設定欄位值
- 設定欄位值是通過
Field.set(Object, Object)
實現的,其中第一個Object
引數是指定的例項,第二個Object
引數是待修改的值。1 import java.lang.reflect.Field; 2 3 public class Main { 4 5 public static void main(String[] args) throws Exception { 6 Person p = new Person("Xiao Ming"); 7 System.out.println(p.getName()); // "Xiao Ming" 8 Class c = p.getClass(); 9 Field f = c.getDeclaredField("name"); 10 f.setAccessible(true); 11 f.set(p, "Xiao Hong"); 12 System.out.println(p.getName()); // "Xiao Hong" 13 } 14 } 15 16 class Person { 17 private String name; 18 19 public Person(String name) { 20 this.name = name; 21 } 22 23 public String getName() { 24 return this.name; 25 } 26 }
- 設定欄位值是通過
- Java的反射API提供的
Field
類封裝了欄位的所有資訊:- 通過
Class
例項的方法可以獲取Field
例項:getField()
,getFields()
,getDeclaredField()
,getDeclaredFields()
; - 通過Field例項可以獲取欄位資訊:
getName()
,getType()
,getModifiers()
; - 通過Field例項可以讀取或設定某個物件的欄位,如果存在訪問限制,要首先呼叫
setAccessible(true)
來訪問非public
欄位。 - 通過反射讀寫欄位是一種非常規方法,它會破壞物件的封裝。
- 通過
-
- 呼叫方法
-
Class
類提供了以下幾個方法來獲取Method
:Method getMethod(name, Class...)
:獲取某個public
的Method
(包括父類)Method getDeclaredMethod(name, Class...)
:獲取當前類的某個Method
(不包括父類)Method[] getMethods()
:獲取所有public
的Method
(包括父類)Method[] getDeclaredMethods()
:獲取當前類的所有Method
(不包括父類)1 public class Main { 2 public static void main(String[] args) throws Exception { 3 Class stdClass = Student.class; 4 // 獲取public方法getScore,引數為String: 5 System.out.println(stdClass.getMethod("getScore", String.class)); 6 // 獲取繼承的public方法getName,無引數: 7 System.out.println(stdClass.getMethod("getName")); 8 // 獲取private方法getGrade,引數為int: 9 System.out.println(stdClass.getDeclaredMethod("getGrade", int.class)); 10 } 11 } 12 13 class Student extends Person { 14 public int getScore(String type) { 15 return 99; 16 } 17 private int getGrade(int year) { 18 return 1; 19 } 20 } 21 22 class Person { 23 public String getName() { 24 return "Person"; 25 } 26 }
-
一個
Method
物件包含一個方法的所有資訊:getName()
:返回方法名稱,例如:"getScore"
;getReturnType()
:返回方法返回值型別,也是一個Class例項,例如:String.class
;getParameterTypes()
:返回方法的引數型別,是一個Class陣列,例如:{String.class, int.class}
;getModifiers()
:返回方法的修飾符,它是一個int
,不同的bit表示不同的含義。
- 如果用反射來呼叫
substring
方法。1 import java.lang.reflect.Method; 2 3 public class Main { 4 public static void main(String[] args) throws Exception { 5 // String物件: 6 String s = "Hello world"; 7 // 獲取String substring(int)方法,引數為int: 8 Method m = String.class.getMethod("substring", int.class); 9 // 在s物件上呼叫該方法並獲取結果: 10 String r = (String) m.invoke(s, 6); 11 // 列印呼叫結果: 12 System.out.println(r); 13 } 14 }
-