前言
在日常的學習工作當中,有一些知識是我們在讀書的時候就能夠習得;但有一些知識不是的,需要在實踐的時候才能得到真知——這或許就是王陽明提倡的“知行合一”。
在java中,并不是所有的類型信息都能在編譯階段明確,有一些類型信息需要在運行時才能確定,這種機制被稱為rtti,英文全稱為run-time type identification,即運行時類型識別,有沒有一點“知行合一”的味道?運行時類型識別主要由class類實現。
01 class類
在java中,我們常用“class”(首字母為小寫的c)關鍵字來定義一個類,說這個類是對某一類對象的抽象。你比如說王二是一個網絡知名作者,我們可以這樣簡單地定義作者類:
1
2
3
4
5
6
|
package com.cmower.java_demo.fifteen; class author { private string pen_name; private string real_name; } |
現在,我們想知道writer這個類本身的一些信息(比如說類名),該怎么辦呢?這時候就需要用到“class”(首字母為大寫的c)類,該類包含了與類有關的信息。請看以下代碼:
1
2
3
4
5
6
7
8
|
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對象的類名。
想象一下,經過五年的刻意練習,王二從一名寫作愛好者晉升為一名作家了。我們用代碼來假裝一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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對象。
一旦class對象加載成功,就可以用它來創建這種類型的所有對象。這也就是說,每個對象在運行時都會有對應的class對象,這個class對象包含了這個對象的類型信息。因此,我們能夠通過class對象知道某個對象“真正”的類型,并不會因為向上轉型而丟失。
02 獲取class對象的其他方式
在使用getclass()方法獲取一個類的class對象時,我們必須要先獲取這個類的對象,比如上面提到的wanger。如果我們之前沒有獲取這個類的對象,就需要用另外兩種方式來獲取類的class對象:
1
2
3
4
5
6
7
8
9
|
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語句塊中。
03 class類提供的常用方法
class類為我們提供了一些非常有用的方法,比如說getname()用來返回類名,getpackage()返回類所在的包名。
我們還可以利用class類提供的newinstance()方法來創建相應類的對象,比如:
1
2
3
4
5
6
7
8
9
10
|
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。我們可以在此基礎上進行改進,示例如下:
1
2
3
4
5
6
7
8
9
10
|
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(); } |
04 反射
我們還可以通過getfields()獲取所有public修飾的字段,通過getmethods()返回所有public修飾的方法。
甚至,我們還可以通過getdeclaredfields()獲取更多字段,包括公共、受保護、默認(包)訪問和私有字段,但不包括繼承字段。對應的,getdeclaredmethods()用來獲取更多方法。示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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的內部其實用的就是反射機制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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); } } |
05 總結
為了完成這篇文章,我特意和青苗谷的一名技術專家聊了聊,問他了幾個很傻逼的問題:“‘運行時'是什么意思?是站在java虛擬機的角度,還是程序員的角度?”
他給了我很好的解釋和啟發,我不由覺得非常的慚愧,作為一名年紀頗長的java學習者,竟然對理論知識薄弱到令人發指的地步——不知道你是否也有這樣的困惑?
但寫作的好處就在于此,在向讀者解釋“java如何在運行時識別類型信息”的過程中,我的思路逐漸地清晰了起來——這真是一個自我提升的好辦法!
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/qing-gee/p/10313662.html