- 什麼是反射?
- 反射就是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 }
-
呼叫靜態方法
-
如果獲取到的Method表示一個靜態方法,呼叫靜態方法時,由於無需指定例項物件,所以
invoke
方法傳入的第一個引數永遠為null
。以Integer.parseInt(String)
為例。1 import java.lang.reflect.Method; 2 3 public class Main { 4 public static void main(String[] args) throws Exception { 5 // 獲取Integer.parseInt(String)方法,引數為String: 6 Method m = Integer.class.getMethod("parseInt", String.class); 7 // 呼叫該靜態方法並獲取結果: 8 Integer n = (Integer) m.invoke(null, "12345"); 9 // 列印呼叫結果: 10 System.out.println(n); 11 } 12 }
-
-
呼叫非public方法
-
和Field類似,對於非public方法,雖然可以通過
Class.getDeclaredMethod()
獲取該方法例項,但直接對其呼叫將得到一個IllegalAccessException
。為了呼叫非public方法,通過Method.setAccessible(true)
允許其呼叫。import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Person p = new Person(); Method m = p.getClass().getDeclaredMethod("setName", String.class); m.setAccessible(true); m.invoke(p, "Bob"); System.out.println(p.name); } } class Person { String name; private void setName(String name) { this.name = name; } }
-
-
多型
-
一個
Person
類定義了hello()
方法,並且它的子類Student
也覆寫了hello()
方法,那麼,從Person.class
獲取的Method
,作用於Student
例項時(如下)。結果可知使用反射呼叫方法時,仍然遵循多型原則:即總是呼叫實際型別的覆寫方法(如果存在)。1 import java.lang.reflect.Method; 2 3 public class Main { 4 public static void main(String[] args) throws Exception { 5 // 獲取Person的hello方法: 6 Method h = Person.class.getMethod("hello"); 7 // 對Student例項呼叫hello方法: 8 h.invoke(new Student()); 9 } 10 } 11 12 class Person { 13 public void hello() { 14 System.out.println("Person:hello"); 15 } 16 } 17 18 class Student extends Person { 19 public void hello() { 20 System.out.println("Student:hello"); 21 } 22 }
通過設定
setAccessible(true)
來訪問非public
方法;
-
-
-
呼叫構造方法
-
通過反射來建立新的例項,可以呼叫Class提供的newInstance()方法:
Person p = Person.class.newInstance();
呼叫Class.newInstance()的侷限是,它只能呼叫該類的public無引數構造方法。如果構造方法帶有引數,或者不是public,就無法直接通過Class.newInstance()來呼叫。
-
為了呼叫任意的構造方法,Java的反射API提供了Constructor物件,它包含一個構造方法的所有資訊,可以建立一個例項。Constructor物件和Method非常類似,不同之處僅在於它是一個構造方法,並且,呼叫結果總是返回例項。
1 import java.lang.reflect.Constructor; 2 3 public class Main { 4 public static void main(String[] args) throws Exception { 5 // 獲取構造方法Integer(int): 6 Constructor cons1 = Integer.class.getConstructor(int.class); 7 // 呼叫構造方法: 8 Integer n1 = (Integer) cons1.newInstance(123); 9 System.out.println(n1); 10 11 // 獲取構造方法Integer(String) 12 Constructor cons2 = Integer.class.getConstructor(String.class); 13 Integer n2 = (Integer) cons2.newInstance("456"); 14 System.out.println(n2); 15 } 16 }
-
通過Class例項獲取Constructor的方法
getConstructor(Class...)
:獲取某個public
的Constructor
;getDeclaredConstructor(Class...)
:獲取某個Constructor
;getConstructors()
:獲取所有public
的Constructor
;getDeclaredConstructors()
:獲取所有Constructor
。
注意
Constructor
總是當前類定義的構造方法,和父類無關,因此不存在多型的問題。呼叫非
public
的Constructor
時,必須首先通過setAccessible(true)
設定允許訪問。setAccessible(true)
可能會失敗。
-
-
獲取繼承關係
- 有了
Class
例項,還可以獲取它的父類的Class。
public class Main { public static void main(String[] args) throws Exception { Class i = Integer.class; Class n = i.getSuperclass(); System.out.println(n); Class o = n.getSuperclass(); System.out.println(o); System.out.println(o.getSuperclass()); } }
-
由於一個類可能實現一個或多個介面,通過
Class
可以查詢到實現的介面型別。import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Class s = Integer.class; Class[] is = s.getInterfaces(); for (Class i : is) { System.out.println(i); } } }
getInterfaces()
只返回當前類直接實現的介面型別,並不包括其父類實現的介面型別。對所有interface
的Class
呼叫getSuperclass()
返回的是null
,獲取介面的父介面要用getInterfaces()。如果一個類沒有實現任何
interface
,那麼getInterfaces()
返回空陣列。 - 判斷一個例項是否是某個型別時,正常情況下,使用
instanceof
操作符,如果是兩個Class
例項,要判斷一個向上轉型是否成立,可以呼叫isAssignableFrom()。
// Integer i = ? Integer.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Integer // Number n = ? Number.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Number // Object o = ? Object.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Object // Integer i = ? Integer.class.isAssignableFrom(Number.class); // false,因為Number不能賦值給Integer
- 有了