簡體   English   中英

使用 Java-8 時可變參數中的 ClassCastException

[英]ClassCastException in varargs while using Java-8

以下代碼適用於m2()但在我使用m1()時拋出ClassCastException

m1m2之間的唯一區別是參數的數量。

public class Test  {

  public static void m1() {
        m3(m4("1"));
    }

    public static void m2() {
        m3(m4("1"), m4("2"));
    }

    public static void m3(Object... str) {
        for (Object o : str) {
            System.out.println(o);
        }
    }

    public static <T> T m4(Object s) {
        return (T) s;
    }

    public static void main(String[] args) {
        m1();
   }
 }

我的問題是 - 當我們使用泛型時,varargs 是否不適用於單個參數?

PS :這與使用泛型和可變參數的 ClassCastException無關

讓我們跳過您暫時忽略了未經檢查的強制轉換警告這一事實,並嘗試了解為什么會發生這種情況。

在此聲明中:

Test.m3(Test.m4("1"));

有一種推斷類型,即m4的返回類型。 如果要在m3調用上下文之外使用它,如:

Test.m4("1"); // T is Object

T被推斷為Object 可以使用類型見證來強制編譯器使用給定類型:

Test.<String>m4("1"); // T is String

...或通過在賦值上下文中使用表達式:

String resString = Test.m4("1"); // T is String
Integer resInt = Test.m4("1"); // T is Integer <-- see the problem?

...或在調用上下文中:

Integer.parseInt(Test.m4("1")); // T is String
Long.toString(Test.m4("1")); // T is Long

現在,回到Test.m3(Test.m4("1")); :我找不到這方面的參考,但我相信編譯器被迫將T解析為m3的參數類型,即Object[] 這意味着T必須m3的參數類型一致,因此被解析為Object[] ,這就好像您將泛型類型指定為:

Test.m3(Test.<Object[]>m4("1")); // this is what is happening

現在,因為m4沒有返回Object[] ,所以m3正在接收一個String ,這會導致不可避免的ClassCastException

如何解決?

解決此問題的第一種方法是為m4指定正確的類型參數:

Test.m3(Test.<String>m4("1")); 

有了這個, Stringm4的返回類型,而m3是用單個String對象調用的(對於Object... var-arg),就像你寫的一樣:

String temp = m4("1");
m3(temp);

@Ravindra Ranwala 刪除的答案中建議了第二種方法。 在我看來,這歸結為注意編譯器警告:

public static <T> T m4(Object s) {
    return (T) s; // unchecked cast
} 

未經檢查的強制轉換警告只是告訴您編譯器(和運行時)不會強制執行類型兼容性,因為T不知道您在哪里強制轉換。 以下版本是類型安全的,但它也使編譯器使用String作為m4的返回類型以及m3的參數類型:

public static <T> T m4(T s) {
    return s;
}

有了這個, m3(m4("1")); 仍然使用Object...作為m3的參數類型,同時保持Stringm4的返回類型(即使用字符串值作為Object數組的第一個元素)。

因為在方法實現中,只read and nothing is stored in the array數組,數組中read and nothing is stored in the array 然而,如果一個方法要在數組中存儲一些東西,它可能會嘗試在數組中存儲一個外來對象,比如將HashMap<Long,Long>放入HashMap<String,String>[] 編譯器和運行時系統都無法阻止它。

下面是另一個example ,說明忽略有關數組構造和變量參數列表發出的警告的潛在危險。

static <T> T[] method_1(T t1, T t2) { 
            return method_2(t1, t2);                       // unchecked warning 
        } 
        static <T> T[] method_2( T... args) { 
            return args; 
        } 
        public static void main(String... args) { 
            String[] strings = method_1("bad", "karma");     // ClassCastException 
        } 

警告:[unchecked] 為 varargs 參數創建類型為 T[] 的未檢查泛型數組

        return method_2(t1, t2); 

與前面的示例一樣,數組的組件類型是不可具體化的,並且由於類型擦除,編譯器不會創建 T[] ,而是創建 Object[] 。 以下是編譯器生成的內容:

示例(同上,按類型擦除翻譯后):

public final class Test {  
        static Object[] method_1( Object t1, Object t2) { 
            return method_2( new Object[] {t1, t2} );                   // unchecked warning 
        } 
        static Object[] method_2( Object[] args) { 
            return args; 
        } 
        public static void main(String[] args) { 
            String[] strings = (String[]) method_1("bad", "karma");       // ClassCastException 
        } 
}

發出未經檢查的警告是為了提醒您注意類型安全違規和意外 ClassCastExceptions 的潛在風險

在該示例中,您將在main()方法中觀察到ClassCastException ,其中將兩個字符串傳遞給第一個方法。 在運行時,兩個字符串被塞進一個Object[] 注意, not a String[]

第二種方法接受Object[] as an argument ,因為在類型擦除后Object[]是其聲明的參數類型。 因此,第二個方法返回一個Object[] , not a String[] ,后者作為第一個方法的返回值傳遞。 最終,編譯器生成的main()方法中的強制轉換失敗了, because the return value of the first method is an Object[] and no String[]

結論

最好避免在需要可變參數列表的地方提供不可具體化類型的對象。 您將始終收到未經檢查的警告,除非您確切地知道調用的方法是做什么的,否則您永遠無法確定調用是類型安全的。

由於編譯期間的泛型類型擦除,您必須使用 T 的 Class 實例進行轉換

public class Test {

    public static void m1() {
        m3(m4("1", String.class));
    }

    public static void m2() {
        m3(m4("1", String.class), m4("2", String.class));
    }

    public static void m3(final Object... str) {
        for (Object o : str) {
            System.out.println(o);
        }
    }

    public static <T> T m4(final Object s, Class<T> clazz) {
        return clazz.cast(s);
    }

    public static void main(String[] args) {
        m1();
        m2();
    }
}
$java Test
1
1
2

可變參數和泛型在 Java 中不能很好地混合。 這是因為

  • 通過在運行時具有相應類型的數組(在您的情況下為 Object 數組)來實現 Varags
  • 數組和泛型只是不兼容。 你不能有一個字符串列表數組。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM