動態語言javascript
靜態語言java
反射就是Reflection,Java的反射是指程序在運行期能夠拿到一個對象的全部信息。spring
正常狀況下,若是咱們要調用一個對象的方法,或者訪問一個對象的字段,一般會傳入對象實例:編程
// Main.java import com.itranswarp.learnjava.Person; public class Main { String getFullName(Person p) { return p.getFirstName() + " " + p.getLastName(); } }
可是,若是不能得到Person
類,只有一個Object
實例,好比這樣:數組
String getFullName(Object obj) { return ??? }
怎麼辦?有童鞋會說:強制轉型啊!安全
String getFullName(Object obj) { Person p = (Person) obj; return p.getFirstName() + " " + p.getLastName(); }
強制轉型的時候,你會發現一個問題:編譯上面的代碼,仍然須要引用Person
類。否則,去掉import
語句,你看能不能編譯經過?框架
因此,反射是爲了解決在運行期,對某個實例一無所知的狀況下,如何調用其方法。dom
<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1gges29pxjoj30tw0t2q34.jpg" alt="image-20200205095809050" style="zoom:50%;float:left" />編程語言
String類、Dog類在方法區中的類文件代碼中,存了屬性、構造方法、方法等信息,其實在Class中也就定義了Field、Constructor、Method這些信息,至關於定義一個對象的接口函數
<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggerrn9w2xj30u00coaaq.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />
除了int
等基本類型外,Java的其餘類型所有都是class
(包括interface
)。例如:
String
Object
Runnable
Exception
仔細思考,咱們能夠得出結論:class
(包括interface
)的本質是數據類型(Type
)。無繼承關係的數據類型沒法賦值:
Number n = new Double(123.456); // OK String s = new Double(123.456); // compile error!
而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);
這個Class
實例是JVM內部建立的,若是咱們查看JDK源碼,能夠發現Class
類的構造方法是private
,只有JVM能建立Class
實例,咱們本身的Java程序是沒法建立Class
實例的。
因此,JVM持有的每一個Class
實例都指向一個數據類型(class
或interface
):
┌───────────────────────────┐ │ Class Instance │──────> String ├───────────────────────────┤ │name = "java.lang.String" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Random ├───────────────────────────┤ │name = "java.util.Random" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Runnable ├───────────────────────────┤ │name = "java.lang.Runnable"│ └───────────────────────────┘
一個Class
實例包含了該class
的全部完整信息:
┌───────────────────────────┐ │ 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.forName()
Class cls = Class.forName("java.lang.String");
由於Class
實例在JVM中是惟一的,因此,上述方法獲取的Class
實例是同一個實例。能夠用==
比較兩個Class
實例:
Class cls1 = String.class; String s = "Hello"; Class cls2 = s.getClass(); boolean sameClass = cls1 == cls2; // true
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
實例。
public class Main { public static void main(String[] args) { printClassInfo("".getClass()); printClassInfo(Runnable.class); printClassInfo(java.time.Month.class); printClassInfo(String[].class); printClassInfo(int.class); } static void printClassInfo(Class cls) { System.out.println("Class name: " + cls.getName()); System.out.println("Simple name: " + cls.getSimpleName()); if (cls.getPackage() != null) { System.out.println("Package name: " + cls.getPackage().getName()); } System.out.println("is interface: " + cls.isInterface()); System.out.println("is enum: " + cls.isEnum()); System.out.println("is array: " + cls.isArray()); System.out.println("is primitive: " + cls.isPrimitive()); } }
注意到數組(例如String[]
)也是一種Class
,並且不一樣於String.class
,它的類名是[Ljava.lang.String
。此外,JVM爲每一種基本類型如int也建立了Class
,經過int.class
訪問。
若是獲取到了一個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時才加載。例如:
// Main.java public class Main { public static void main(String[] args) { if (args.length > 0) { create(args[0]); } } static void create(String name) { Person p = new Person(name); } }
當執行Main.java
時,因爲用到了Main
,所以,JVM首先會把Main.class
加載到內存。然而,並不會加載Person.class
,除非程序執行到create()
方法,JVM發現須要加載Person
類時,纔會首次加載Person.class
。若是沒有執行create()
方法,那麼Person.class
根本就不會被加載。
這就是JVM動態加載class
的特性。
動態加載class
的特性對於Java程序很是重要。利用JVM動態加載class
的特性,咱們才能在運行期根據條件加載不一樣的實現類。例如,Commons Logging老是優先使用Log4j,只有當Log4j不存在時,才使用JDK的logging。利用JVM動態加載特性,大體的實現代碼以下:
反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰。
Java 反射主要提供如下功能:
咱們先看看如何經過Class
實例獲取字段信息。Class
類提供瞭如下幾個方法來獲取字段:
public class Main { public static void main(String[] args) throws Exception { Class stdClass = Student.class; // 獲取public字段"score": System.out.println(stdClass.getField("score")); // 獲取繼承的public字段"name": System.out.println(stdClass.getField("name")); // 獲取private字段"grade": System.out.println(stdClass.getDeclaredField("grade")); } } class Student extends Person { public int score; private int grade; } class Person { public String name; }
上述代碼首先獲取Student
的Class
實例,而後,分別獲取public
字段、繼承的public
字段以及private
字段,打印出的Field
相似:
public int Student.score public java.lang.String Person.name private int Student.grade
一個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
字段的值:
public class Main { public static void main(String[] args) throws Exception { Object p = new Person("Xiao Ming"); Class c = p.getClass(); Field f = c.getDeclaredField("name"); Object value = f.get(p); System.out.println(value); // "Xiao Ming" } } class Person { private String name; public Person(String name) { this.name = name; } }
上述代碼先獲取Class
實例,再獲取Field
實例,而後,用Field.get(Object)
獲取指定實例的指定字段的值。
運行代碼,若是不出意外,會獲得一個IllegalAccessException
,這是由於name
被定義爲一個private
字段,正常狀況下,Main
類沒法訪問Person
類的private
字段。要修復錯誤,能夠將private
改成public
,或者,在調用Object value = f.get(p);
前,先寫一句:
f.setAccessible(true);
調用Field.setAccessible(true)
的意思是,別管這個字段是否是public
,一概容許訪問。
能夠試着加上上述語句,再運行代碼,就能夠打印出private
字段的值。
有童鞋會問:若是使用反射能夠獲取private
字段的值,那麼類的封裝還有什麼意義?
答案是正常狀況下,咱們老是經過p.name
來訪問Person
的name
字段,編譯器會根據public
、protected
和private
決定是否容許訪問字段,這樣就達到了數據封裝的目的。
而反射是一種很是規的用法,使用反射,首先代碼很是繁瑣,其次,它更多地是給工具或者底層框架來使用,目的是在不知道目標實例任何信息的狀況下,獲取特定字段的值。
此外,setAccessible(true)
可能會失敗。若是JVM運行期存在SecurityManager
,那麼它會根據規則進行檢查,有可能阻止setAccessible(true)
。例如,某個SecurityManager
可能不容許對java
和javax
開頭的package
的類調用setAccessible(true)
,這樣能夠保證JVM核心庫的安全。
經過Field實例既然能夠獲取到指定實例的字段值,天然也能夠設置字段的值。
設置字段值是經過Field.set(Object, Object)
實現的,其中第一個Object
參數是指定的實例,第二個Object
參數是待修改的值。示例代碼以下:
public class Main { public static void main(String[] args) throws Exception { Person p = new Person("Xiao Ming"); System.out.println(p.getName()); // "Xiao Ming" Class c = p.getClass(); Field f = c.getDeclaredField("name"); f.setAccessible(true); f.set(p, "Xiao Hong"); System.out.println(p.getName()); // "Xiao Hong" } } class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return this.name; } }
咱們已經能經過Class
實例獲取全部Field
對象,一樣的,能夠經過Class
實例獲取全部Method
信息。Class
類提供瞭如下幾個方法來獲取Method
:
Method getMethod(name, Class...)
:獲取某個public
的Method
(包括父類)Method getDeclaredMethod(name, Class...)
:獲取當前類的某個Method
(不包括父類)Method[] getMethods()
:獲取全部public
的Method
(包括父類)Method[] getDeclaredMethods()
:獲取當前類的全部Method
(不包括父類)public class Main { public static void main(String[] args) throws Exception { Class stdClass = Student.class; // 獲取public方法getScore,參數爲String: System.out.println(stdClass.getMethod("getScore", String.class)); // 獲取繼承的public方法getName,無參數: System.out.println(stdClass.getMethod("getName")); // 獲取private方法getGrade,參數爲int: System.out.println(stdClass.getDeclaredMethod("getGrade", int.class)); } } class Student extends Person { public int getScore(String type) { return 99; } private int getGrade(int year) { return 1; } } class Person { public String getName() { return "Person"; } }
上述代碼首先獲取Student
的Class
實例,而後,分別獲取public
方法、繼承的public
方法以及private
方法,打印出的Method
相似:
public int Student.getScore(java.lang.String) public java.lang.String Person.getName() private int Student.getGrade(int)
一個Method
對象包含一個方法的全部信息:
getName()
:返回方法名稱,例如:"getScore"
;getReturnType()
:返回方法返回值類型,也是一個Class實例,例如:String.class
;getParameterTypes()
:返回方法的參數類型,是一個Class數組,例如:{String.class, int.class}
;getModifiers()
:返回方法的修飾符,它是一個int
,不一樣的bit表示不一樣的含義。當咱們獲取到一個Method
對象時,就能夠對它進行調用。咱們如下面的代碼爲例:
String s = "Hello world"; String r = s.substring(6); // "world"
若是用反射來調用substring
方法,須要如下代碼:
public class Main { public static void main(String[] args) throws Exception { // String對象: String s = "Hello world"; // 獲取String substring(int)方法,參數爲int: Method m = String.class.getMethod("substring", int.class); // 在s對象上調用該方法並獲取結果: String r = (String) m.invoke(s, 6); // 打印調用結果: System.out.println(r); } }
注意到substring()
有兩個重載方法,咱們獲取的是String substring(int)
這個方法。思考一下如何獲取String substring(int, int)
方法。
對Method
實例調用invoke
就至關於調用該方法,invoke
的第一個參數是對象實例,即在哪一個實例上調用該方法,後面的可變參數要與方法參數一致,不然將報錯
若是獲取到的Method表示一個靜態方法,調用靜態方法時,因爲無需指定實例對象,因此invoke
方法傳入的第一個參數永遠爲null
。咱們以Integer.parseInt(String)
爲例:
public class Main { public static void main(String[] args) throws Exception { // 獲取Integer.parseInt(String)方法,參數爲String: Method m = Integer.class.getMethod("parseInt", String.class); // 調用該靜態方法並獲取結果: Integer n = (Integer) m.invoke(null, "12345"); // 打印調用結果: System.out.println(n); } }
和Field相似,對於非public方法,咱們雖然能夠經過Class.getDeclaredMethod()
獲取該方法實例,但直接對其調用將獲得一個IllegalAccessException
。爲了調用非public方法,咱們經過Method.setAccessible(true)
容許其調用:
此外,setAccessible(true)
可能會失敗。若是JVM運行期存在SecurityManager
,那麼它會根據規則進行檢查,有可能阻止setAccessible(true)
。例如,某個SecurityManager
可能不容許對java
和javax
開頭的package
的類調用setAccessible(true)
,這樣能夠保證JVM核心庫的安全。
一個Person
類定義了hello()
方法,而且它的子類Student
也覆寫了hello()
方法,那麼,從Person.class
獲取的Method
,做用於Student
實例時,調用的方法究竟是哪一個?
public class Main { public static void main(String[] args) throws Exception { // 獲取Person的hello方法: Method h = Person.class.getMethod("hello"); // 對Student實例調用hello方法: h.invoke(new Student()); } } class Person { public void hello() { System.out.println("Person:hello"); } } class Student extends Person { public void hello() { System.out.println("Student:hello"); } }
運行上述代碼,發現打印出的是Student:hello
,所以,使用反射調用方法時,仍然遵循多態原則:即老是調用實際類型的覆寫方法(若是存在)。上述的反射代碼:
Method m = Person.class.getMethod("hello"); m.invoke(new Student());
實際上至關於:
Person p = new Student(); p.hello();
咱們一般使用new
操做符建立新的實例:
Person p = new Person();
若是經過反射來建立新的實例,能夠調用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
調用Class.newInstance()的侷限是,它只能調用該類的public無參數構造方法。若是構造方法帶有參數,或者不是public,就沒法直接經過Class.newInstance()來調用。
爲了調用任意的構造方法,Java的反射API提供了Constructor對象,它包含一個構造方法的全部信息,能夠建立一個實例。Constructor對象和Method很是相似,不一樣之處僅在於它是一個構造方法,而且,調用結果老是返回實例:
public class Main { public static void main(String[] args) throws Exception { // 獲取構造方法Integer(int): Constructor cons1 = Integer.class.getConstructor(int.class); // 調用構造方法: Integer n1 = (Integer) cons1.newInstance(123); System.out.println(n1); // 獲取構造方法Integer(String) Constructor cons2 = Integer.class.getConstructor(String.class); Integer n2 = (Integer) cons2.newInstance("456"); System.out.println(n2); } }
經過Class實例獲取Constructor的方法以下:
getConstructor(Class...)
:獲取某個public
的Constructor
;getDeclaredConstructor(Class...)
:獲取某個Constructor
;getConstructors()
:獲取全部public
的Constructor
;getDeclaredConstructors()
:獲取全部Constructor
。注意Constructor
老是當前類定義的構造方法,和父類無關,所以不存在多態的問題。
調用非public
的Constructor
時,必須首先經過setAccessible(true)
設置容許訪問。setAccessible(true)
可能會失敗。
因爲一個類可能實現一個或多個接口,經過Class
咱們就能夠查詢到實現的接口類型。例如,查詢Integer
實現的接口:
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); } } }
運行上述代碼可知,Integer
實現的接口有:
要特別注意:getInterfaces()
只返回當前類直接實現的接口類型,並不包括其父類實現的接口類型:
public class Main { public static void main(String[] args) throws Exception { Class s = Integer.class.getSuperclass(); Class[] is = s.getInterfaces(); for (Class i : is) { System.out.println(i); } } }
Integer
的父類是Number
,Number
實現的接口是java.io.Serializable
。
此外,對全部interface
的Class
調用getSuperclass()
返回的是null
,獲取接口的父接口要用getInterfaces()
:
System.out.println(java.io.DataInputStream.class.getSuperclass()); // java.io.FilterInputStream,由於DataInputStream繼承自FilterInputStream System.out.println(java.io.Closeable.class.getSuperclass()); // null,對接口調用getSuperclass()老是返回null,獲取接口的父接口要用getInterfaces()
當咱們判斷一個實例是不是某個類型時,正常狀況下,使用instanceof
操做符:
Object n = Integer.valueOf(123); boolean isDouble = n instanceof Double; // false boolean isInteger = n instanceof Integer; // true boolean isNumber = n instanceof Number; // true boolean isSerializable = n instanceof java.io.Serializable; // true
若是是兩個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
反射機制是構建框架技術的基礎所在,使用反射能夠避免將代碼寫死在框架中。正是反射有以上的特徵,因此它能動態編譯和建立對象,極大的激發了編程語言的靈活性,強化了多態的特性,進一步提高了面向對象編程的抽象能力,於是受到編程界的青睞。
儘管反射機制帶來了極大的靈活性及方便性,但反射也有缺點。反射機制的功能很是強大,但不能濫用。在能不使用反射完成時,儘可能不要使用,緣由有如下幾點:
Java反射機制中包含了一些動態類型,因此Java虛擬機不可以對這些動態代碼進行優化。所以,反射操做的效率要比正常操做效率低不少。咱們應該避免在對性能要求很高的程序或常常被執行的代碼中使用反射。並且,如何使用反射決定了性能的高低。若是它做爲程序中較少運行的部分,性能將不會成爲一個問題。
使用反射一般須要程序的運行沒有安全方面的限制。若是一個程序對安全性提出要求,則最好不要使用反射。
反射容許代碼執行一些一般不被容許的操做,因此使用反射有可能會致使意想不到的後果。反射代碼破壞了Java程序結構的抽象性,因此當程序運行的平臺發生變化的時候,因爲抽象的邏輯結構不能被識別,代碼產生的效果與以前會產生差別。
IOC:即「控制反轉」,不是什麼技術,而是一種思想。使用IOC意味着將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。在Spring的配置文件中,常常看到以下配置:
<bean id="userService" class="com.tim.wang.sourcecode.reflection.springioc.UserServiceImpl"></bean>
那麼經過這樣配置,Spring是怎麼幫咱們實例化對象,而且放到容器中去了了,就是經過反射!!!
//解析<bean .../>元素的id屬性獲得該字符串值爲「courseDao」 String idStr = "courseDao"; //解析<bean .../>元素的class屬性獲得該字符串值爲「com.qcjy.learning.Dao.impl.CourseDaoImpl」 String classStr = "com.qcjy.learning.Dao.impl.CourseDaoImpl"; //利用反射知識,經過classStr獲取Class類對象 Class<?> cls = Class.forName(classStr); //實例化對象 Object obj = cls.newInstance(); //container表示Spring容器 container.put(idStr, obj);
當一個類裏面須要應用另外一類的對象時,Spring的配置以下所示:
<bean id="courseService" class="com.qcjy.learning.service.impl.CourseServiceImpl"> <!-- 控制調用setCourseDao()方法,將容器中的courseDao bean做爲傳入參數 --> <property name="courseDao" ref="courseDao"></property> </bean>
繼續用僞代碼的形式來模擬實現一下Spring底層處理原理:
//解析<property .../>元素的name屬性獲得該字符串值爲「courseDao」 String nameStr = "courseDao"; //解析<property .../>元素的ref屬性獲得該字符串值爲「courseDao」 String refStr = "courseDao"; //生成將要調用setter方法名 String setterName = "set" + nameStr.substring(0, 1).toUpperCase() + nameStr.substring(1); //獲取spring容器中名爲refStr的Bean,該Bean將會做爲傳入參數 Object paramBean = container.get(refStr); //獲取setter方法的Method類,此處的cls是剛纔反射代碼獲得的Class對象 Method setter = cls.getMethod(setterName, paramBean.getClass()); //調用invoke()方法,此處的obj是剛纔反射代碼獲得的Object對象 setter.invoke(obj, paramBean);