[英]ClassCastException is System.out.println
如果我直接在System.out.println()中使用t.getTheString(implClass.class)的返回對象,則下面的代碼將引發java.lang.ClassCastException。 雖然當我將對象分配給某些引用時,它可以正常工作,為什么?
interface testInterface{
public String testMethod();
}
class testClass{
public <T extends testInterface> T getTheString(Class< ? extends testInterface> classType){
try {
testInterface obj = classType.newInstance();
return (T)obj;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
class implClass implements testInterface{
@Override
public String testMethod() {
return "Sample String";
}
@Override
public String toString(){
return super.toString() + " Overridden toString()";
}
}
public class genericTest {
public static void main(String args[]){
testClass t = new testClass();
testInterface ti = t.getTheString(implClass.class);
implClass i = t.getTheString(implClass.class);
//System.out.println(i); //<= Works fine
//System.out.println(ti); //<= Works fine
System.out.println(t.getTheString(implClass.class)); // Exception in thread "main" java.lang.ClassCastException: implClass cannot be cast to java.lang.String
}
}
編輯:許多人對此問題感到困惑。 使用上限只是為了創建此示例。 我知道這是不正確的。 疑問是,如果您對上述代碼進行反編譯,您會看到編譯器將t.getTheString(implClass.class)結果轉換為String,因為可能不確定該類型。 我的問題是,如果將其類型轉換為Object會更好。 在這種情況下,它將調用toString()方法,並且代碼可以正常工作。 這是Java編譯器的問題,還是我在這里錯過了任何東西?
在其他答案中已經提到了問題的原因-您的classType
參數可以是實現testInterface
任何類型,而不必是T
這是一個解決方案。
當前的問題是classType
不受約束,並且沒有足夠的上下文,因此無法正確推斷T
,從而導致obj
被強制轉換為錯誤的類型。 因此,如果我們將方法更改為此:
public <T extends testInterface> T getTheString(Class<T> classType){
每次可以從參數正確推斷出T
!
另一個解決方案是用所需的類型來填充編譯器:
System.out.println((testInterface)t.getTheString(implClass.class));
從您的評論到另一個答案:
如果編譯器不確定類型,為什么不將其類型轉換為Object而不是String
編譯器只能選擇調用println
兩個重載,因為其他重載是原始類型。 它可以是println(Object)
或println(String)
。 根據JLS(第15.12.2節):
此步驟使用方法的名稱和參數表達式的類型來查找可訪問和適用的方法。可能有不止一種這樣的方法, 在這種情況下,選擇了最具體的方法。
有關更多信息,請參見此帖子 。
由於字符串比對象更具體,因此選擇后者。
泛型在運行時被擦除,因此發生的情況是,當JVM需要選擇要調用System.out.println()
哪個變體時,它並不暗示它是什么暗示,並假定傳入類為String
並采用System.out.println(String string)
並且您的implClass
實例顯然不是String
。
所有這些都是使用泛型而不是在類級別上聲明的。 更改testClass
聲明以聲明您的通用T
class testClass<T> {
完成后,在implClass i = t.getTheString(implClass.class);
嘗試將testInterface
轉換為implClass
時,編譯器應開始抱怨缺少testInterface
轉換implClass i = t.getTheString(implClass.class);
一旦您像這樣糾正它: implClass i = (implClass) t.getTheString(implClass.class);
System.out.println()
將按預期開始工作。
如果您想知道為什么編譯器會錯誤地選擇方法,請考慮以下問題:-T類型未在類級別聲明,因此在編譯過程中將其刪除時,它將變為“任何”,-當編譯器稍后嘗試查找時調用println()
方法的匹配變體,首先要進行匹配,因為類型已變為“ anything”,所以第一個方法變體將進行匹配-在PrintStream的Oracle JDK impl中,第一個impl恰好是將String作為參數的那個(檢查源代碼;)
對我來說很好; 重命名並運行為Main.java:
> javac Main.java
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
> java Main
implClass@15dc8287 Overridden toString()
然后:
> javac -Xlint:unchecked Main.java
Main.java:10: warning: [unchecked] unchecked cast
return (T)obj;
^
required: T
found: testInterface
where T is a type-variable:
T extends testInterface declared in method <T>getTheString(Class<? extends testInterface>)
這里的問題是:在您的方法getTheString()中,您將其強制轉換為T; 但不能保證傳入的類與該T有任何關系! 實現該接口的類不一定也是T。
除了您看到的例外; 如前所述-無法復制(您這一邊的設置問題)?
如果取消注釋兩個“行得通”的行,並通過反編譯器(我使用http://www.javadecompilers.com/ )運行genericTest.class,您將看到:
public static void main(String[] arrstring) {
testClass testClass2 = new testClass();
Object t = testClass2.getTheString(implClass.class);
implClass implClass2 = (implClass)testClass2.getTheString(implClass.class);
System.out.println((Object)implClass2);
System.out.println(t);
System.out.println((String)testClass2.getTheString(implClass.class));
}
對println
的前兩個調用調用了一個接受Object的方法,該對象依次調用toString()
並打印您期望的結果。 在第三個println
,編譯器期望結果實際上是一個String,然后嘗試進行強制類型轉換,這是從中獲取異常的地方。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.