我們都知道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和類型指針兩部分。

泛型信息以參數(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