日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

「深入Java」类型信息:RTTI和反射

發布時間:2025/3/21 java 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 「深入Java」类型信息:RTTI和反射 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1.RTTI Run-Time Type Infomation 運行時類型信息

為什么需要RTTI?

越是優秀的面向對象設計,越是強調高內聚低耦合,正如依賴倒轉原則所說:“無論是高層模塊還是低層模塊,都應該針對抽象編程”。

比如說我們有一個抽象父類:

1 Shape draw()

以下是三個具體類:

1 2 3 Circle draw() Square draw() Triangle draw()

某些情況下,我們持有Shape,但卻遠遠不夠——因為我們想要針對它的具體類型進行特殊處理,然而我們的設計完全針對抽象,所以在當前上下文環境中無法判斷具體類型。
因為RTTI的存在,使得我們在不破壞設計的前提下得以達到目的。


Class類與Class對象

事實上,每一個類都持有其對應的Class類的對象的引用(Object類中的getClass()能讓我們獲取到它),其中包含著與類相關的信息。
非常容易注意到,針對每一個類,編譯Java文件會生成一個二進制.class文件,這其中就保存著該類對應的Class對象的信息。

.class是用于供類加載器使用的文件

Java程序在運行之前并沒有被完全加載,各個部分是在需要時才被加載的。

為了使用類而作的準備包含三步:

  • 加載。由類加載器執行,查找字節碼,創建一個Class對象。
  • 鏈接。驗證字節碼,為靜態域分配存儲空間,如果必需的話,會解析這個類創建的對其他類的所有引用(比如說該類持有static域)。
  • 初始化。如果該類有超類,則對其初始化,執行靜態初始化器[注]和靜態初始化塊。
  • 注:原文為static initializers,經查看Thinking in Java,其意應為靜態域在定義處的初始化,如:
    static Dog d = new Dog(0);。

    所有的類都是在對其第一次使用時,動態加載到JVM中去的。當程序創建第一個對類的靜態成員的引用時,JVM會使用類加載器來根據類名查找同名的.class——一旦某個類的Class對象被載入內存,它就被用來創建這個類的所有對象。構造器也是類的靜態方法,使用new操作符創建新對象會被當作對類的靜態成員的引用。
    注意特例:如果一個static final值是編譯期常量,讀取這個值不需要對類進行初始化。所以說對于不變常量,我們總是應該使用static final修飾。


    Class.forName(String str)

    Class類有一個很有用的靜態方法forName(String str),可以讓我們對于某個類不進行創建就得到它的Class對象的引用,例如這個樣子:

    1 2 3 4 5 try { ?????Class toyClass = Class.forName("com.duanze.Toy"); // 注意必須使用全限定名 } catch (ClassNotFoundException e) { }

    然而,使用forName(String str)有一個副作用:如果Toy類沒有被加載,調用它會觸發Toy類的static子句(靜態初始化塊)。

    與之相比,更好用的是類字面常量,像是這樣:

    1 Class toyClass = Toy.class;

    支持編譯時檢查,所以不會拋出異常。使用類字面常量創建Class對象的引用與forName(String str)不同,不會觸發Toy類的static子句(靜態初始化塊)。所以,更簡單更安全更高效。
    類字面常量支持類、接口、數組、基本數據類型。


    ×拓展×

    Class.forName(String className)使用裝載當前類的類裝載器來裝載指定類。因為class.forName(String className)方法內部調用了Class.forName(className, true, this.getClass().getClassLoader())方法,如你所見,第三個參數就是指定類裝載器,顯而易見,它指定的是裝載當前類的類裝載器的實例,也就是this.getClass().getClassLoader();

    你可以選擇手動指定裝載器:

    1 2 ClassLoader cl = new? ClassLoader();?? Class c1 = cl.loadClass(String className, boolean resolve );

    更詳細的參考


    范化的Class引用

    通過范型以及通配符,我們能對Class對象的引用進行類型限定,像是:

    1 Class<Integer> intClass = int.class; // 注意右邊是基本數據類型的類字面常量

    這樣做的好處是能讓編譯器進行額外的類型檢查。
    知道了這一點以后,我們可以把之前的例子改寫一下:

    1 2 Class toyClass = Toy.class; Class<?> toyClass = Toy.class;

    雖然這兩句是等價的,但從可讀性來說Class<?>要優于Class,這說明編程者并不是由于疏忽而選擇了非具體版本,而是特意選擇了非具體版本。


    Class.newInstance()

    既然拿到了包含著類信息的Class對象的引用,我們理應可以構造出一個類的實例。Class.newInstance()就是這樣一個方法,比如:

    1 2 3 4 5 6 7 8 9 10 11 // One try { ????Class<?> toyClass = Class.forName("com.duanze.Toy"); ????Object obj = toyClass.newInstance(); } catch (ClassNotFoundException e) { } // Two Class<?> toyClass = Toy.class; Object obj = toyClass.newInstance();

    使用newInstance()創建的類,必須帶有默認構造器。
    由于toyClass僅僅只是一個Class對象引用,在編譯期不具備更進一步的類型信息,所以你使用newInstance()時只會得到一個Object引用。如果你需要拿到確切類型,需要這樣做:

    1 2 Class<Toy> toyClass = Toy.class; Toy obj = toyClass.newInstance();

    但是,如果你遇到下面的情況,還是只能拿到Object引用:

    1 2 3 4 5 6 Class<SubToy> subToyClass = SubToy.class; Class<? super SubToy> upClass = subToy.getSuperclass(); // 希望拿到SubToy的父類Toy的Class對象引用 // This won't compile: // Class<Toy> upClass = subToy.getSuperclass(); // Only produces Object: Object obj = upClass.newInstance();

    雖然從常理上來講,編譯器應該在編譯期就能知道SubToy的超類是Toy,但實際上卻并不支持這樣寫:

    1 2 // This won't compile: Class<Toy> upClass = subToy.getSuperclass();

    而只能夠接受:

    1 Class<? super SubToy> upClass = subToy.getSuperclass(); // 希望拿到SubToy的父類Toy

    這看上去有些奇怪,但現狀就是如此,我們惟有接受。好在這并不是什么大問題,因為轉型操作并不困難。


    類型檢查

    在進行類型轉換之前,可以使用instanceof關鍵字進行類型檢查,像是:

    1 2 3 if ( x instanceof Shape ) { ?????Shape s = (Shape)x; }

    一般情況下instanceof已經夠用,但有些時候你可能需要更動態的測試途徑:Class.isInstance(Class clz):

    1 2 Class<Shape> s = Shape.class; s.isInstance(x);

    可以看到,與instanceof相比,isInstance()的左右兩邊都是可變的,這一動態性有時可以讓大量包裹在if else...中的instanceof縮減為一句。


    2.反射

    不知道你注意到了沒有,以上使用的RTTI都具有一個共同的限制:在編譯時,編譯器必須知道所有要通過RTTI來處理的類。

    但有的時候,你獲取了一個對象引用,然而其對應的類并不在你的程序空間中,怎么辦?(這種情況并不少見,比如說你從磁盤文件或者網絡中獲取了一串字串,并且被告知這一串字串代表了一個類,這個類在編譯器為你的程序生成代碼之后才會出現。)

    Class類和java.lang.reflect類庫一同對反射的概念提供了支持。反射機制并沒有什么神奇之處,當通過反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,看它屬于哪個特定的類。因此,那個類的.class對于JVM來說必須是可獲取的,要么在本地機器上,要么從網絡獲取。所以對于RTTI和反射之間的真正區別只在于:

    • RTTI,編譯器在編譯時打開和檢查.class文件
    • 反射,運行時打開和檢查.class文件

    明白了以上概念后,什么getFields(),getMethods(),getConstructors()之類的方法基本上全都可以望文生義了。

    我們可以看一下Android開發中經常用的對于ActionBar,讓Overflow中的選項顯示圖標這一效果是怎么做出來的:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /* overflow中的Action按鈕應不應該顯示圖標, 是由MenuBuilder這個類的setOptionalIconsVisible方法來決定的, 如果我們在overflow被展開的時候給這個方法傳入true, 那么里面的每一個Action按鈕對應的圖標就都會顯示出來了。 */ @Override public boolean onMenuOpened(int featureId, Menu menu) { ????if (featureId == Window.FEATURE_ACTION_BAR && menu != null) { ????????if (menu.getClass().getSimpleName().equals("MenuBuilder")) { ????????????try { ????????????????// Boolean.TYPE 同 boolean.class ????????????????Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); ????????????????// 通過setAccessible(true),確保可以調用方法——即使是private方法 ????????????????m.setAccessible(true); ????????????????// 相當于:menu.setOptionalIconsVisible(true) ????????????????m.invoke(menu, true); ????????????} catch (Exception e) { ????????????} ????????} ????} ????return super.onMenuOpened(featureId, menu); }

    ×拓展:動態代理×

    Java中對于反射的一處重要使用為動態代理,可以參考這篇IBM developerworks的文章


    參考資料

    • Java編程思想
    • http://blog.csdn.net/guolin_blog/article/details/18234477#t7
    • http://yanwushu.sinaapp.com/class_forname/
    from:?http://www.importnew.com/21458.html

    總結

    以上是生活随笔為你收集整理的「深入Java」类型信息:RTTI和反射的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。