[英]Java generics, type erasure and type of a generic member
Java有類型擦除,人們說在運行時無法確定通用對象的類型而沒有黑客攻擊。 請考慮以下代碼
public class TestClass<T> {
private T genericField;
public TestClass(T genericField) {
this.genericField = genericField;
}
public void printTypeInfo() {
System.out.println("Hi I'm a " + genericField.getClass());
System.out.println("Am I a string? " + (genericField instanceof String));
System.out.println("Am I a long? " + (genericField instanceof Long));
}
public static void main(String [] args) {
TestClass<String> genericString = new TestClass<>("Hello");
TestClass<Long> genericLong = new TestClass<>(111111L);
genericString.printTypeInfo();
System.out.println("------------------");
genericLong.printTypeInfo();
}
}
它給了我以下結果:
Hi I'm a class java.lang.String
Am I a string? true
Am I a long? false
------------------
Hi I'm a class java.lang.Long
Am I a string? false
Am I a long? true
似乎類型信息在運行時很容易獲得。 我在這里錯過了什么?
TestClass<Number> genericNumber = new TestClass<>(42L);
genericNumber.printTypeInfo();
這將打印Hi I'm a Long
而不是Hi I'm a Number
。 您可以看到genericField
是Long
但您無法看到T
被實例化為Number
。
這是一個由於類型擦除而無法做的事情的例子。
TestClass<?> generic = new TestClass<String>("Hello");
if (generic instanceof TestClass<String>) {
System.out.println("It holds a string!");
}
else if (generic instanceof TestClass<Long>) {
System.out.println("It holds a long!");
}
您可以在運行時確定genericField
中任何給定對象的類型,但是您無法在運行時確定TestClass<X>
和TestClass<Y>
之間的差異,而無需檢查您知道碰巧受泛型類型約束的某些成員。 也就是說,如果僅給出一個TestClass
實例,則無法確定TestClass<...>
的類型參數。
您的代碼顯示genericField
的值的類型, 而不是 TestClass
實例的參數化類型。 嘗試打印this.getClass()
,你會發現它在兩種情況下都是相同的。
你“缺少”的是這樣的:你(可以理解)在genericField
本身擁有一個對象(帶有一個類型)和TestClass
有一個泛型類型參數這一事實之間建立了一個不正確的聯系。 您可以通過確定為TestClass
指定的類型參數的能力來確定genericField
值的類型。 也就是說,雖然您可以根據您對genericField
為T
知識推斷出類型參數是什么,但這與直接確定T
是什么不同,這是不可能的。
查看上一段的另一種方法是考慮以下幾點:
如果TestClass
沒有類型為T
成員,則沒有其他方法可以提取T
您的代碼僅“確定” T
基於您自己的個人知識,即genericField
被聲明為持有相同類型(因此其中的對象必須屬於該類型,因此您可以得出結論通用參數可能是相同的)類型或某種超類型)。
如果你沒有使用泛型,而genericField
只是一個Object
,你仍然可以在genericField
確定對象的類型。 也就是說,它的類型與泛型類型“獨立”,除非使用泛型,否則編譯器會對類型進行約束。 編譯后它仍然只是一個任意對象,無論你是否使用泛型(這實際上只是一種方便,因為你可以在沒有泛型的情況下完成所有這些,只需使用Object
和大量的強制轉換)。
還要考慮TestClass<Base>
的可能性,其中genericField
被賦予了Derived
。 您的代碼將正確顯示genericField
是Derived
,但您無法知道類型參數是Base
與Derived
,因為信息已被刪除。
此外,將以上幾點進一步歸結為家:
TestClass<String> genericString = new TestClass<String>("Hello");
TestClass<?> kludge = genericString;
TestClass<Long> genericLongButNotReally = (TestClass<Long>)kludge;
genericLongButNotReally.printTypeInfo();
輸出String
的信息(這就是給出那些“未經檢查的轉換”警告的原因,以防止出現這種奇怪的事情),而不關心使用Long
類型參數指定genericLongButNotReally
的事實。 當您使用泛型類型時,必須克服編譯器提供的良好保護; 但在運行時它並不關心。
能夠獲得變量的類型和對象的類型是兩回事。
僅僅因為你可以得到genericField
的類型並不意味着你可以看到T是一個數字。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.