究竟java泛型真正含义是什么(非类型擦除)

    作者:课课家教育更新于: 2019-04-16 14:39:55

    大神带你学编程,欢迎选课

    今天我们说说究竟java泛型真正含义是什么(非类型擦除),大家对JavaEE感兴趣或要学习的,跟课课家小编一起看看究竟java泛型真正含义是什么(非类型擦除)是什么。我们用简短的几段描述下,大家认真听哦。昨天,在逛论坛时遇到个这么个问题,代码如下:

    public class GenericTest { //方法一 public static > List sort(List list) { return Arrays.asList(list.toArray((T[]) new Comparable[list.size()])); } //方法二 public static > T[] sort2(List list) { // 这里没报错 return list.toArray((T[]) new Comparable[list.size()]); } public static void main(String[] args) { List list = new ArrayList<>(); list.add(1); list.add(2); // 方法一调用正常 System.out.println(sort(list).getClass()); // 方法二调用报错了,这里报错了 System.out.println(sort2(list).getClass()); } }

    这个问题有以下四个现象:

    (1)方法一调用完全正常;

    (2)方法二调用报错了;

    (3)方法二报错的地方是在System.out.println(sort2(list).getClass());这行,而不是return list.toArray((T[]) new Comparable[list.size()]);这行;

    (4)报的错是[Ljava.lang.Comparable; cannot be cast to [Ljava.lang.Integer;

    怎么样?你心中有答案嘛?类型擦除?怎么擦?摩擦摩擦?

    解决

    刚拿到这道题,我也是一脸懵逼,这要报错也应该是在return list.toArray((T[]) new Comparable[list.size()]);这行啊,而且要报错应该两个方法都报错啊。

    抱着不放弃不抛弃的心态,彤哥做了大量的实验,终于得出了泛型的本质,且听我娓娓道来。

    小插曲

    首先,我们要明白,java中的数组是不支持向下转型的,但是javaweb入门" href="http://www.kokojia.com/s87032/" target="_blank">JavaWeb入门如果本身就是那个类型的是可以转过去的,请看下面的例子:

    public static void main(String[] args) {
        Object[] objs = new Object[]{1};
        // 类型转换错误
    //  Integer[] ins = (Integer[]) objs;
    
        Object[] objs2 = new Integer[]{1};
        // 不报错
        Integer[] ins2 = (Integer[]) objs2;
    
    }
    
    

    类型擦除

    java里的泛型是假泛型,只在编译期有效,在运行时是没有泛型的概念的,举个简单的例子:

    public static void main(String[] args) {
            List strList = Arrays.asList("1");
            List intList = Arrays.asList(1);
    
            // 打印:true
            System.out.println(strList.getClass() == intList.getClass());
        }
    

    可以看到两个list的类型是一样的,如果你觉得这个例子不够说服力,那我给你个过分点的例子:

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        List strList = new ArrayList<>();
    
        Method addMethod = strList.getClass().getMethod("add", Object.class);
        addMethod.invoke(strList, 1);
        addMethod.invoke(strList, true);
        addMethod.invoke(strList, new Long(1));
        addMethod.invoke(strList, new Byte[]{1});
    
        // 打印:[1, true, 1, 1]
        System.out.println(strList);
    }
    

    瞧,我可以往一个String类型的List中扔任何我想扔的东西,服不服?!

    所以说java里面的泛型是假的,运行时不存在滴。

    回归正题

    spring数组不能向下强转我懂了,类型擦除我也懂了,似乎还是过不好这一生,呃不是,是还是解决不了这道题啊?

    呃,好像是~~

    我们再来看一个简单的例子:

    // GenericTest2.java(源码)
    public class GenericTest2 {
        public static void main(String[] args) {
            System.out.println(raw("1"));
        }
    
        public static  T raw(T t) {
            return t;
        }
    }
    // GenericTest2.class(反编译)
    public class GenericTest2 {
        public GenericTest2() {
        }
    
        public static void main(String[] args) {
            System.out.println((String)raw("1"));
        }
    
        public static  T raw(T t) {
            return t;
        }
    }
    

    嗯~似乎看出来点端倪,反编译后多了个构造方法。

    呃,没错。还有呢?

    仔细一看,System.out.println((String)raw("1"));这一句多加了个String强转。

    这就是关键所在,结合类型擦除,运行时并没有所谓的泛型,所以raw()返回的其实是Object,但是调用者自己知道我要的是String类型啊,所以我就知道强转一下喽。

    我们再来看个极端的例子:

    // GenericTest2.java(源码)
    public class GenericTest2 {
        public static void main(String[] args) {
            System.out.println(raw("1"));
        }
    
        public static  T raw(T t) {
            return (T)new Integer(1);
        }
    }
    // GenericTest2.class(反编译)
    public class GenericTest2 {
        public GenericTest2() {
        }
    
        public static void main(String[] args) {
            System.out.println((String)raw("1"));
        }
    
        public static  T raw(T t) {
            return new Integer(1);
        }
    }
    

    仔细观察,可以发现,raw()方法里的强转(T)new Integer(1)变成了new Integer(1),强转被擦除了,实际上在运行时这里的T变成了Object,所有类型都是Object的子类,也就不需要强转了。

    (String)raw("1")的强转还是加上的,这是调用者知道类型是String,所以raw()返回后自己强转成String一下。

    当然,这个代码运行是会报错的,java.lang.Integer cannot be cast to java.lang.String,因为raw()返回的是Integer类型,强转成String类型失败了。

    好了,基本思路就是这样。

    泛型类呢?

    我们上面举的例子都是泛型方法,那么泛型类呢?

    同样地,我们来看个例子:

    // GenericTest3.java(源码)
    public class GenericTest3 {
        public static void main(String[] args) {
            System.out.println(new Raw().raw("1"));
        }
    }
    class Raw<T> {
        public T raw(T t) {
            return (T)new Integer(1);
        }
    }
    // GenericTest3.class(反编译)
    public class GenericTest3 {
        public GenericTest3() {
        }
    
        public static void main(String[] args) {
            System.out.println((String)(new Raw()).raw("1"));
        }
    }
    class Raw<T> {
        Raw() {
        }
    
        public T raw(T t) {
            return new Integer(1);
        }
    }

    今天的内容就讲到这边,可以看到,跟泛型方法的表现一模一样。当然,这里运行时也会报java.lang.Integer cannot be cast to java.lang.String这个错误。java中的泛型只在编译期有效,在运行时只有调用者知道需要什么类型,且调用者调用泛型方法后自己做强制转换,被调用者是完全无感的。所以,出现问题不要问被调用者,而是要问调用者,你丫是怎么调用的?!

课课家教育

未登录