若是有人再問你 Java 的反射,把這篇文章扔給他

在 Java 中,並非全部的類型信息都能在編譯階段明確,有一些類型信息須要在運行時才能肯定,這種機制被稱爲 RTTI,英文全稱爲 Run-Time Type Identification,即運行時類型識別,有沒有一點「知行合一」的味道?運行時類型識別主要由Class類實現。java

在平常的學習工做當中,有一些知識是咱們在讀書的時候就可以習得;但有一些知識不是的,須要在實踐的時候才能獲得真知——這或許就是王陽明提倡的「知行合一」。apache

0一、 Class類

在Java中,咱們經常使用「class」(首字母爲小寫的c)關鍵字來定義一個類,說這個類是對某一類對象的抽象。你好比說王二是一個網絡知名做者,咱們能夠這樣簡單地定義做者類:json

package com.cmower.java_demo.fifteen; class Author { private String pen_name; private String real_name; } 

如今,咱們想知道Writer這個類自己的一些信息(好比說類名),該怎麼辦呢?這時候就須要用到「Class」(首字母爲大寫的C)類,該類包含了與類有關的信息。請看如下代碼:數組

public class Test { public static void main (String [] args) { Author wanger = new Author(); Class c1 = wanger.getClass(); System.out.println(c1.getName()); //輸出 com.cmower.java_demo.fifteen.Author } } 

當咱們建立了做者對象wanger後,就能夠經過wanger.getClass()獲取wanger的Class對象,經過c1.getName()可得到wanger對象的類名。安全

想象一下,通過五年的刻意練習,王二從一名寫做愛好者晉升爲一名做家了。咱們用代碼來僞裝一下:網絡

package com.cmower.java_demo.fifteen; class Author { private String pen_name; private String real_name; } class Writer extends Author { private String honour; } public class Test { public static void main (String [] args) { Author wanger = new Writer(); Class c1 = wanger.getClass(); System.out.println(c1.getName()); //輸出 com.cmower.java_demo.fifteen.Writer } } 

在上例中,即便咱們將Writer的對象引用wanger向上轉型爲Author,wanger的Class對象類型依然是Writer(經過輸出結果能夠斷定)。這也就是說,Java可以在運行時自動識別類型的信息,它不會由於wanger的引用類型是Author而丟失wanger真正的類型信息(Writer)。Java是怎麼作到這一點呢?學習

當Java建立某個類的對象,好比Writer類對象時,Java會檢查內存中是否有相應的Class對象。若是內存中沒有相應的Class對象,那麼Java會在.class文件中尋找Writer類的定義,並加載Writer類的Class對象。spa

一旦Class對象加載成功,就能夠用它來建立這種類型的全部對象。這也就是說,每一個對象在運行時都會有對應的Class對象,這個Class對象包含了這個對象的類型信息。所以,咱們可以經過Class對象知道某個對象「真正」的類型,並不會由於向上轉型而丟失。debug

0二、 獲取Class對象的其餘方式

在使用getClass()方法獲取一個類的Class對象時,咱們必需要先獲取這個類的對象,好比上面提到的wanger。若是咱們以前沒有獲取這個類的對象,就須要用另外兩種方式來獲取類的Class對象:code

Class c2 = Writer.class; System.out.println(c2.getName()); try { Class c3 = Class.forName("com.cmower.java_demo.fifteen.Writer"); System.out.println(c3.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } 

1)當使用.class來獲取Class對象時,不會自動地初始化該Class對象,初始化被延遲到了對靜態方法或者非final靜態域進行首次引用時才執行。這樣作不只更簡單,並且更安全,由於它在編譯時就會受到檢查(所以不須要置於try語句塊中)。

2)Class.forName會自動地初始化該Class對象,但須要指定類名,而且須要置於try語句塊中。

0三、 Class類提供的經常使用方法

Class類爲咱們提供了一些很是有用的方法,好比說getName()用來返回類名,getPackage()返回類所在的包名。

咱們還能夠利用Class類提供的newInstance()方法來建立相應類的對象,好比:

Class c2 = Writer.class; System.out.println(c2.getName()); try { Writer wangsan = (Writer) c2.newInstance(); System.out.println(wangsan); // 輸出:com.cmower.java_demo.fifteen.Writer@7852e922 } catch (InstantiationException | IllegalAccessException e1) { e1.printStackTrace(); } 

因爲咱們在建立Class對象c2時沒有使用泛型,因此newInstance()返回的對象類型須要強轉爲Writer。咱們能夠在此基礎上進行改進,示例以下:

Class<Writer> c4 = Writer.class; System.out.println(c4.getName()); try { Writer wangsan = c4.newInstance(); System.out.println(wangsan); // 輸出:com.cmower.java_demo.fifteen.Writer@7852e922 } catch (InstantiationException | IllegalAccessException e1) { e1.printStackTrace(); } 

0四、 反射

咱們還能夠經過getFields()獲取全部public修飾的字段,經過getMethods()返回全部public修飾的方法。

甚至,咱們還能夠經過getDeclaredFields()獲取更多字段,包括公共、受保護、默認(包)訪問和私有字段,但不包括繼承字段。對應的,getDeclaredMethods()用來獲取更多方法。示例以下:

package com.cmower.java_demo.fifteen; import java.lang.reflect.Field; import java.lang.reflect.Method; class Author { private String pen_name; private String real_name; } class Writer extends Author { private String honour; private void makeMoney() { System.out.println("不少不少錢"); } } public class Test { public static void main(String[] args) { Class<Writer> c4 = Writer.class; System.out.println(c4.getName()); try { Writer wangsan = c4.newInstance(); System.out.println(wangsan); Field[] fields = c4.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); } Method[] methods = c4.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName()); } } catch (InstantiationException | IllegalAccessException e1) { e1.printStackTrace(); } } } 

上面的例子其實涉及到了反射,Field、Method(還有例子中未提到的Constructor)都來自java.lang.reflect類庫。Class類與java.lang.reflect類庫一塊兒對反射的概念進行了支持。

有時候,咱們須要從磁盤文件或網絡文件中讀取一串字節碼,並把它轉換成一個類,這時候就須要用到反射。最多見的典型例子就是將一串JSON字符串(在網絡傳輸中最初的形態多是字節數組)反射爲對應類型的對象。

阿里巴巴提供的FastJSON提供了 toJSONString()parseObject() 方法來將 Java 對象與 JSON 相互轉換。調用toJSONString方法便可將對象轉換成 JSON 字符串,parseObject 方法則反過來將 JSON 字符串轉換成對象。FastJSON的內部其實用的就是反射機制。

package com.cmower.common.util; import java.io.UnsupportedEncodingException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.alibaba.fastjson.JSON; @SuppressWarnings("all") public class JsonUtil { private static Log logger = LogFactory.getLog("json"); public static byte[] objectToByte(Object obj) throws UnsupportedEncodingException { String jsonStr = JSON.toJSONString(obj); logger.debug("序列化後數據:" + jsonStr); return jsonStr.getBytes("UTF-8"); } public static <T> T byteToObject(byte[] data, Class<T> obj) throws UnsupportedEncodingException { String objectString = new String(data, "UTF-8"); logger.debug("反序列化後數據 : " + objectString); return JSON.parseObject(objectString, obj); } public static <T> Object stringToObject(String data, Class<T> obj) throws UnsupportedEncodingException { logger.debug("反序列化後數據 : " + data); return JSON.parseObject(data, obj); } } 

 

本文首發於java黑洞網,博客園同步更新

相關文章
相關標籤/搜索