簡體   English   中英

在Java中轉換為泛型類型不會引發ClassCastException?

[英]Casting to generic type in Java doesn't raise ClassCastException?

我遇到了一個奇怪的Java行為,看起來像個bug。 是嗎? 即使對象不是K的實例,將Object轉換為泛型類型(例如, K )也不會拋出ClassCastException 這是一個例子:

import java.util.*;
public final class Test {
  private static<K,V> void addToMap(Map<K,V> map, Object ... vals) {
    for(int i = 0; i < vals.length; i += 2)
      map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException!
  }
  public static void main(String[] args) {
    Map<String,Integer> m = new HashMap<String,Integer>();
    addToMap(m, "hello", "world"); //No exception
    System.out.println(m.get("hello")); //Prints "world", which is NOT an Integer!!
  }
}

更新 :感謝cletus和Andrzej Doyle提供的有用答案。 因為我只能接受一個,所以我接受了Andrzej Doyle的回答,因為它讓我找到了一個我認為並不太糟糕的解決方案。 我認為這是在單行中初始化小地圖的一種更好的方法。

  /**
   * Creates a map with given keys/values.
   * 
   * @param keysVals Must be a list of alternating key, value, key, value, etc.
   * @throws ClassCastException if provided keys/values are not the proper class.
   * @throws IllegalArgumentException if keysVals has odd length (more keys than values).
   */
  public static<K,V> Map<K,V> build(Class<K> keyClass, Class<V> valClass, Object ... keysVals)
  {
    if(keysVals.length % 2 != 0)
      throw new IllegalArgumentException("Number of keys is greater than number of values.");

    Map<K,V> map = new HashMap<K,V>();
    for(int i = 0; i < keysVals.length; i += 2)
      map.put(keyClass.cast(keysVals[i]), valClass.cast(keysVals[i+1]));

    return map;
  }

然后你這樣稱呼它:

Map<String,Number> m = MapBuilder.build(String.class, Number.class, "L", 11, "W", 17, "H", 0.001);

Java泛型使用類型擦除,這意味着那些參數化類型不會在運行時保留,因此這是完全合法的:

List<String> list = new ArrayList<String>();
list.put("abcd");
List<Integer> list2 = (List<Integer>)list;
list2.add(3);

因為編譯后的字節碼看起來更像是這樣的:

List list = new ArrayList();
list.put("abcd");
List list2 = list;
list2.add(3); // auto-boxed to new Integer(3)

Java泛型只是構建Object的語法糖。

正如cletus所說,擦除意味着你不能在運行時檢查這個(並且由於你的轉換,你無法在編譯時檢查它)。

請記住,泛型是僅編譯時功能。 集合對象沒有任何通用參數,只有您為該對象創建的引用 這就是為什么如果你需要從原始類型甚至Object轉發一個集合,你會得到很多關於“unchecked cast”的警告 - 因為編譯器無法驗證該對象是否具有正確的泛型類型(如對象本身沒有泛型類型)。

另外,請記住鑄造意味着什么 - 它是告訴編譯器的一種方式“我知道你不一定能檢查類型是否匹配,但相信我 ,我知道他們這樣做”。 當你覆蓋類型檢查(錯誤)然后最終導致類型不匹配時,誰會受到責備? ;-)

看來你的問題在於缺乏異構的通用數據結構。 我建議您的方法的類型簽名應該更像private static<K,V> void addToMap(Map<K,V> map, List<Pair<K, V>> vals) ,但我不相信這真的讓你有所收獲。 對列表基本上是一個映射,因此為了調用方法而構造typesafe vals參數與直接填充映射一樣多。

如果你真的,真的想讓你的課程大致保持原樣但添加運行時類型安全性,或許以下內容會給你一些想法:

private static<K,V> void addToMap(Map<K,V> map, Object ... vals, Class<K> keyClass, Class<V> valueClass) {
  for(int i = 0; i < vals.length; i += 2) {
    if (!keyClass.isAssignableFrom(vals[i])) {
      throw new ClassCastException("wrong key type: " + vals[i].getClass());
    }
    if (!valueClass.isAssignableFrom(vals[i+1])) {
      throw new ClassCastException("wrong value type: " + vals[i+1].getClass());
    }

    map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException!
  }
}

Java的泛型是使用“類型擦除”完成的,這意味着在運行時,代碼不知道你有一個Map <String,Integer> - 它只是看到一個Map。 因為你正在將這些東西轉換為Objects(通過你的addToMap函數的參數列表),所以在編譯時,代碼“看起來正確”。 它在編譯時不會嘗試運行這些東西。

如果您在編譯時關心類型,請不要將它們稱為Object。 :)使你的addToMap函數看起來像

private static<K,V> void addToMap(Map<K, V> map, K key, V value) {

如果要在地圖中插入多個項目,則需要創建類似java.util的Map.Entry類,並將鍵/值對包裝在該類的實例中。

這是對Generics在Java中做什么和不做什么的一個相當好的解釋: http//www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

它與C#非常不同!

我想你是想嘗試做這樣的事情? 您要添加到地圖的對的編譯時安全性:

addToMap(new HashMap<String, Integer>(), new Entry<String,Integer>("FOO", 2), new Entry<String, Integer>("BAR", 8));

    public static<K,V> void addToMap(Map<K,V> map, Entry<K,V>... entries) {
        for (Entry<K,V> entry: entries) {
            map.put(entry.getKey(), entry.getValue());
        }
    }

    public static class Entry<K,V> {

        private K key;
        private V value;

        public Entry(K key,V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }

評論后編輯:

啊,那么也許所有你真正想要的就是用這種神秘的語法來騷擾那些剛轉向Java的新員工。

Map<String, Integer> map = new HashMap<String,Integer>() {{
  put("Foo", 1);
  put("Bar", 2);
}};

Java泛型僅適用於編譯期間而非運行時。 問題是你實現的方式,java編譯器沒有機會在編譯時確保類型安全。

由於你的K,V從不說它們擴展任何特定的類,在編譯期間java無法知道它應該是一個整數。

如果您更改代碼如下

private static void addToMap(Map map,Object ... vals)

它會給你一個編譯時錯誤

暫無
暫無

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

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