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(Object obj)
返回指定對象上此欄位
表示的欄位的值。
-
-
設置欄位值
- 設置欄位值是通過
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 }
-