遇到個小問題,Java泛型真的是雞肋嗎?

zhanjindong發表於2014-06-22

今天遇到一個小問題,讓我感覺Java的泛型(因為揹負了歷史的包袱導致的)有點雞肋啊。

我們經常會遇到要一些自定義的key-value字串,比如:

"key1:1k;key2:2;key3:3"

通常編碼的時候會將它轉換為一個Map這樣方便操作,因為key和value的型別不一定(可能是int也可能是String等),於是我用Java寫了一個簡單的泛型方法:

@SuppressWarnings("unchecked")
    public static <K, V> Map<K, V> getMap(String source, String firstSplit, String secondSplit) {

        Map<K, V> result = new HashMap<K, V>();
        if (source.equals("")) {
            return result;
        }
        String[] strings = source.split(firstSplit);
        for (int i = 0; i < strings.length; i++) {
            String[] tmp = strings[i].split(secondSplit);
            if (tmp.length == 2) {
                result.put((K) tmp[0], (V) tmp[1]);
                // System.out.println("(K) tmp[0]:"+((K) tmp[0]).getClass());
                // System.out.println("(V) tmp[1]:"+((V) tmp[1]).getClass());
            }
        }

        return result;
    }

看上去貌似可以正常工作的,用上面的字串舉例子,我應該希望得到的是Map<String, Integer>這樣一個結果。

String test = "key1:1k;key2:2;key:3";
        Map<String, Integer> map = getMap(test, ";", ":");

        for (Entry<String, Integer> entry : map.entrySet()) {
            Integer value = entry.getValue();
        }

上面的程式碼編譯時完全沒問題的,但是一執行:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at ossp.demo.generic.GenericDemo.main(GenericDemo.java:38)

報的是型別轉換錯誤,String不能轉換為Interger型別?但是明明entry.getValue()結果就是Interger型別啊,難道不是嗎?

 

一開始很疑惑,但稍微一想就明白了,哦!Java泛型用的是“擦除”法,完全是編譯期的,執行時已經沒有泛型引數的型別資訊了,也就是說執行時所有的泛型引數都被替換成了Object(如果有泛型約束(.net是這麼叫的)是不是就替換成上限型別?)所以上面的泛型方法其實就等價於:

public static Map<Object, Object> getMap(String source, String firstSplit, String secondSplit) {

        Map<Object, Object> result = new HashMap<Object, Object>();
        if (source.equals("")) {
            return result;
        }
        String[] strings = source.split(firstSplit);
        for (int i = 0; i < strings.length; i++) {
            String[] tmp = strings[i].split(secondSplit);
            if (tmp.length == 2) {
                result.put((Object) tmp[0], (Object) tmp[1]);
            }
        }

        return result;
    }

也就是說entry.getValue();返回的其實是一個Object物件的,它的型別應該是java.lang.String,於是我想那麼這樣轉換一下應該可以了:

Integer value = Integer.valueOf(entry.getValue());

但是讓我鬱悶的是仍然報之前的錯誤,滑鼠點上去,智慧提示看執行的應該是引數為int的過載反覆,額,又繞回去了。

那怎麼才能得到我想得到那個Integer的value呢???最後我發現這樣是可以的:

Integer value = Integer.valueOf(String.valueOf(entry.getValue()));

我靠!這太讓我無語了。不光如此我發現直接執行下面這行程式碼也會報型別轉換錯誤:

System.out.println(entry.getValue().getClass());

既然entry.getValue()的型別是java.lang.String,為什麼Map<String, Integer> map = getMap2(test, ";", ":");和Entry<Object, Integer> entry : map.entrySet()這兩行又都能編譯通過呢?想想還是萬惡的“型別擦除“的原因,我們先看看C#裡的情況。

 Dictionary<string, int> dic1 = new Dictionary<string, int>();
 Dictionary<string, double> dic2 = new Dictionary<string, double>();
 Console.WriteLine(dic1);
 Console.WriteLine(dic2);

我們知道.NET泛型將每個型別引數理解為一個獨立的型別,所以上面dic1和dic2的型別是不一樣的:

但是在Java裡因為“型別擦除“實際上Map<String,Interger>和Map<String,Double>的型別都是:java.util.HashMap

Map<String, Integer> map1 = new HashMap<String, Integer>();
Map<String, Double> map2 = new HashMap<String, Double>();
System.out.println(map1.getClass());
System.out.println(map2.getClass());

這樣看來上面的程式碼編譯通過是必須的,那麼這智慧提示有什麼意義呢(編譯期的YY?)。

 

我們看看同樣的問題C#是怎麼解決的。一開始我以為像Java那樣直接強制型別轉換就可以:

或者這樣:

這些都是不行的。但是隻要執行執行時還有型別引數的資訊,那麼肯定是有辦法辦到的,.NET中庫中就有現成的這樣一個方法:Convert.ChangeType,於是我們可以寫出下面這個輔助泛型方法:

    static V GenericCast<U, V>(U obj)
    {
         return (V)Convert.ChangeType(obj, typeof(V));
    }

於是乎為了解決我的問題,我可以寫這樣一個泛型方法了:

   static Dictionary<K, V> ToMap<K, V>(string source, string firstSplit, string secondSpilt)
     {
            Dictionary<K, V> result = new Dictionary<K, V>();

            if (String.IsNullOrEmpty(source))
            {
                return result;
            }

            string[] info1 = source.Split(new string[] { firstSplit }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var item in info1)
            {
                string[] info2 = item.Split(new string[] { secondSpilt }, StringSplitOptions.RemoveEmptyEntries);
                if (info2.Length == 2)
                {
                    result.Add(GenericCast<string, K>(info2[0]), GenericCast<string, V>(info2[1]));
                }
            }

            return result;
      } 
 string test = "key1:1.1;key2:2;key3:3";
 Dictionary<string, double> map = ToMap<string, double>(test, ";", ":");

 foreach (var item in map)
 {
      Console.WriteLine(item.Key + ":" + item.Value);
 }

 

並不是想黑Java,只是之前用C#的泛型用的比較爽,用Java的總感覺有點食之無味,棄之可惜。

 

 

 

相關文章