Java泛型真的是鸡肋吗?

学习Java的同学注意了!!! 
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:286945438 我们一起学Java!


今天遇到一个小问题,让我感觉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的总感觉有点食之无味,弃之可惜。


学习Java的同学注意了!!! 
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:286945438 我们一起学Java!

转载于:https://my.oschina.net/abcijkxyz/blog/851330

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值