深入理解Java泛型

我們都知道Java泛型的實現(xiàn)依賴于類型擦除,但是不可能完全擦除,因為運行時還是要知道泛型的真實類型的,那這個真是類型是如何存儲的,又如何能拿到?順著這個問題可以把Java內(nèi)存劃分、類文件結(jié)構(gòu)和對象內(nèi)存布局串聯(lián)起來。

一、泛型的使用和類型擦除

泛型本質(zhì)上是參數(shù)化類型(paramentersized type)的應(yīng)用,可以聲明在類、接口或者方法上。沒有泛型時可以用Object結(jié)合強制類型轉(zhuǎn)化實現(xiàn)相似需求,但是運行時風(fēng)險就明顯了,泛型就是用于解決這個問題的。
Java泛型并沒有真的新增類型,List<String>和List<Int>在編譯后都是List,依靠的是類型擦除和強制轉(zhuǎn)換來實現(xiàn)的。

Map<String, String> map = HashMap<String, String>()
map.put("Hello", "你好")
System.out.println(map.get("Hello"))

編譯后

Map map = HashMap()
map.put("Hello", "你好")
System.out.println((String) map.get("Hello"))

二、在字節(jié)碼文件中的形式

這里只簡單介紹下相關(guān)基礎(chǔ),Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進制流,各個數(shù)據(jù)項目嚴格按照順序緊湊的排列在Class文件中。
結(jié)構(gòu)中只有無符號數(shù)和表兩種結(jié)構(gòu),無符號數(shù)描述數(shù)字、索引引用、數(shù)量值等,表由無符號數(shù)和其它表組合。
文件中依次為Class版本、常量池、訪問標(biāo)志、父類信息、字段表、方法表等。類、字段表、方法表等都有屬性表,在屬性表中有個Signature屬性就是用于記錄泛型簽名信息的。
屬性表中有個Code屬性,即是方法編譯成后的字節(jié)碼指令序列,泛型擦除僅僅是擦除Code表中的泛型信息。

三、在內(nèi)存中的形式

直接指針模型,對象在堆內(nèi)存中有對象頭和對象實例兩部分,對象頭包括mark word和類型指針兩部分。


Java內(nèi)存模型

泛型信息以參數(shù)化類型信息(paramentersized typ)的方式存儲在對象類型實例(即方法區(qū)中的Class)中,各種泛型實例的類型指針都指向方法區(qū)中同一class類型實例。

 //運行后可以發(fā)現(xiàn)兩個class對象是同一個
 val list1 = ArrayList<String>()
 val list2 = ArrayList<Int>()
 println(list1::class.hashCode().toString())
 println(list2::class.hashCode().toString())

即使這段代碼編譯成 val list1 = ArrayList(),字段list在其方法的字段表中,其中的屬性表Signature屬性記錄著泛型是String還是Int。

四、配合反射獲取泛型信息

我們定義一個泛型類

   open class SuperClass<T> {

    }

    class TestClass: SuperClass<Double>() {
        val name = ArrayList<Float>()

        private fun <E> parameterizedFun(element: E): E {
            return element
        }

        fun testParameter(list: List<String>) {
        }
    }

根據(jù)上面的分析可以知道從參數(shù)化類型信息中就可以得到真實類型

    @Test
    fun testFun() {
        val clazz = TestClass::class.java

        //獲取類泛型
        if (clazz.genericSuperclass is ParameterizedType) {
            val parameterizedType = clazz.genericSuperclass as ParameterizedType
            val parameterClass = parameterizedType.actualTypeArguments[0]  as Class<*>
            println(parameterClass)
        }

        //獲取方法參數(shù)的泛型
        val method = clazz.getMethod("testParameter", List::class.java)
        if (method.genericParameterTypes[0] is ParameterizedType) {
            val parameterizedType = method.genericParameterTypes[0] as ParameterizedType
            val parameterClass = parameterizedType.actualTypeArguments[0]  as Class<*>
            println(parameterClass)
        }

        //獲取變量類型
        val field = clazz.getDeclaredField("name")
        if (field.genericType is ParameterizedType) {
            val parameterizedType = field.genericType as ParameterizedType
            val parameterClass = parameterizedType.actualTypeArguments[0]  as Class<*>
            println(parameterClass)
        }
    }

最后輸出

class java.lang.Double
class java.lang.String
class java.lang.Float
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • [TOC] 深入理解 Java 泛型 概述 泛型的本質(zhì)是參數(shù)化類型,通常用于輸入?yún)?shù)、存儲類型不確定的場景。相比于...
    albon閱讀 5,770評論 0 7
  • 概述 泛型的本質(zhì)是參數(shù)化類型,通常用于輸入?yún)?shù)、存儲類型不確定的場景。相比于直接使用 Object 的好處是:編譯...
    彳亍口巴閱讀 179評論 0 3
  • 簡介 泛型的意思就是參數(shù)化類型,通過使用參數(shù)化類型創(chuàng)建的接口、類、方法,可以指定所操作的數(shù)據(jù)類型。比如:可以使用參...
    零度沸騰_yjz閱讀 3,400評論 1 15
  • ?? 本文已歸檔到:「javacore」?? 本文中的示例代碼已歸檔到:「javacore」 1. 為什么需要泛型 J...
    靜默虛空閱讀 198評論 1 1
  • 說來慚愧,雖然平時經(jīng)常會使用到一些泛型類,但是卻一直沒有深入地去了解過泛型機制。今天開始學(xué)習(xí)記錄泛型機制相關(guān)的知識...
    怡紅快綠閱讀 627評論 0 1

友情鏈接更多精彩內(nèi)容