zoukankan      html  css  js  c++  java
  • 遇到个小问题,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的总感觉有点食之无味,弃之可惜。

  • 相关阅读:
    POJ 3710 Christmas Game#经典图SG博弈
    POJ 2599 A funny game#树形SG(DFS实现)
    POJ 2425 A Chess Game#树形SG
    LeetCode Array Easy 122. Best Time to Buy and Sell Stock II
    LeetCode Array Easy121. Best Time to Buy and Sell Stock
    LeetCode Array Easy 119. Pascal's Triangle II
    LeetCode Array Easy 118. Pascal's Triangle
    LeetCode Array Easy 88. Merge Sorted Array
    ASP.NET MVC 学习笔记之 MVC + EF中的EO DTO ViewModel
    ASP.NET MVC 学习笔记之面向切面编程与过滤器
  • 原文地址:https://www.cnblogs.com/zhanjindong/p/3803114.html
Copyright © 2011-2022 走看看