简单理解java泛型的本质(非类型擦除)
背景
之前在网上发现这个问题
publicclassGenericTest{ //方法一 publicstatic>List sort(List list){ returnArrays.asList(list.toArray((T[])newComparable[list.size()])); } //方法二 publicstatic >T[]sort2(List list){ //这里没报错 returnlist.toArray((T[])newComparable[list.size()]); } publicstaticvoidmain(String[]args){ List list=newArrayList<>(); 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());这行,而不是returnlist.toArray((T[])newComparable[list.size()]);这行;
(4)报的错是[Ljava.lang.Comparable;cannotbecastto[Ljava.lang.Integer;;
怎么样?你心中有答案嘛?类型擦除?怎么擦?摩擦摩擦?
解决
刚拿到这道题,我也是一脸懵逼,这要报错也应该是在returnlist.toArray((T[])newComparable[list.size()]);这行啊,而且要报错应该两个方法都报错啊。
抱着不放弃不抛弃的心态,彤哥做了大量的实验,终于得出了泛型的本质,且听我娓娓道来。
小插曲
首先,我们要明白,java中的数组是不支持向下转型的,但是如果本身就是那个类型的是可以转过去的,请看下面的例子:
publicstaticvoidmain(String[]args){ Object[]objs=newObject[]{1}; //类型转换错误 //Integer[]ins=(Integer[])objs; Object[]objs2=newInteger[]{1}; //不报错 Integer[]ins2=(Integer[])objs2; }
java里的泛型是假泛型,只在编译期有效,在运行时是没有泛型的概念的,举个简单的例子:
publicstaticvoidmain(String[]args){ ListstrList=Arrays.asList("1"); List intList=Arrays.asList(1); //打印:true System.out.println(strList.getClass()==intList.getClass()); }
可以看到两个list的类型是一样的,如果你觉得这个例子不够说服力,那我给你个过分点的例子:
publicstaticvoidmain(String[]args)throwsNoSuchMethodException,InvocationTargetException,IllegalAccessException{ ListstrList=newArrayList<>(); MethodaddMethod=strList.getClass().getMethod("add",Object.class); addMethod.invoke(strList,1); addMethod.invoke(strList,true); addMethod.invoke(strList,newLong(1)); addMethod.invoke(strList,newByte[]{1}); //打印:[1,true,1,1] System.out.println(strList); }
瞧,我可以往一个String类型的List中扔任何我想扔的东西,服不服?!
所以说java里面的泛型是假的,运行时不存在滴。
回归正题
数组不能向下强转我懂了,类型擦除我也懂了,似乎还是过不好这一生,呃不是,是还是解决不了这道题啊?
呃,好像是~~
我们再来看一个简单的例子:
//GenericTest2.java(源码) publicclassGenericTest2{ publicstaticvoidmain(String[]args){ System.out.println(raw("1")); } publicstaticTraw(Tt){ returnt; } } //GenericTest2.class(反编译) publicclassGenericTest2{ publicGenericTest2(){ } publicstaticvoidmain(String[]args){ System.out.println((String)raw("1")); } publicstatic Traw(Tt){ returnt; } }
呃,没错。还有呢?
仔细一看,System.out.println((String)raw("1"));这一句多加了个String强转。
这就是关键所在,结合类型擦除,运行时并没有所谓的泛型,所以raw()返回的其实是Object,但是调用者自己知道我要的是String类型啊,所以我就知道强转一下喽。
我们再来看个极端的例子:
//GenericTest2.java(源码) publicclassGenericTest2{ publicstaticvoidmain(String[]args){ System.out.println(raw("1")); } publicstaticTraw(Tt){ return(T)newInteger(1); } } //GenericTest2.class(反编译) publicclassGenericTest2{ publicGenericTest2(){ } publicstaticvoidmain(String[]args){ System.out.println((String)raw("1")); } publicstatic Traw(Tt){ returnnewInteger(1); } }
仔细观察,可以发现,raw()方法里的强转(T)newInteger(1)变成了newInteger(1),强转被擦除了,实际上在运行时这里的T变成了Object,所有类型都是Object的子类,也就不需要强转了。
而(String)raw("1")的强转还是加上的,这是调用者知道类型是String,所以raw()返回后自己强转成String一下。
当然,这个代码运行是会报错的,java.lang.Integercannotbecasttojava.lang.String,因为raw()返回的是Integer类型,强转成String类型失败了。
好了,基本思路就是这样。
泛型类呢?
我们上面举的例子都是泛型方法,那么泛型类呢?
同样地,我们来看个例子:
//GenericTest3.java(源码) publicclassGenericTest3{ publicstaticvoidmain(String[]args){ System.out.println(newRaw().raw("1")); } } classRaw { publicTraw(Tt){ return(T)newInteger(1); } } //GenericTest3.class(反编译) publicclassGenericTest3{ publicGenericTest3(){ } publicstaticvoidmain(String[]args){ System.out.println((String)(newRaw()).raw("1")); } } classRaw { Raw(){ } publicTraw(Tt){ returnnewInteger(1); } }
可以看到,跟泛型方法的表现一模一样。当然,这里运行时也会报java.lang.Integercannotbecasttojava.lang.String这个错误。
总结
java中的泛型只在编译期有效,在运行时只有调用者知道需要什么类型,且调用者调用泛型方法后自己做强制转换,被调用者是完全无感的。
所以,出现问题不要问被调用者,而是要问调用者,你丫是怎么调用的?!
解答开篇
为了方便我们还是把开篇的问题拿过来。
//GenericTest.java(源码) publicclassGenericTest{ //方法一 publicstatic>List sort(List list){ returnArrays.asList(list.toArray((T[])newComparable[list.size()])); } //方法二 publicstatic >T[]sort2(List list){ //这里没报错 returnlist.toArray((T[])newComparable[list.size()]); } publicstaticvoidmain(String[]args){ List list=newArrayList<>(); list.add(1); list.add(2); //方法一调用正常 System.out.println(sort(list).getClass()); //方法二调用报错了,这里报错了 System.out.println(sort2(list).getClass()); } }
这里似乎又不太一样,变成了
那么,我们就延伸一下,被调用者是完全无感的,它只能尽力拿到它知道的类型,比如这里就只能尽力拿到Comparable,如果是
所以,方法二返回的就是实打实的Comparable[]类型,作为被调用者,它一点问题都没有。
但是,调用方是知道我需要的是Integer[]类型的,因为list里面是Integer类型,所以返回的应该是Integer[]类型,所以我就强转喽,然后就报错了。
到底是不是这样?我们来看看反编译后的代码:
//GenericTest.class(反编译) publicclassGenericTest{ publicGenericTest(){ } publicstatic>List sort(List list){ returnArrays.asList(list.toArray((Comparable[])(newComparable[list.size()]))); } publicstatic >T[]sort2(List list){ //这里使用的是Comparable[]强转,所以返回的也是实打实的Comparable[]类型 return(Comparable[])list.toArray((Comparable[])(newComparable[list.size()])); } publicstaticvoidmain(String[]args){ List list=newArrayList(); list.add(1); list.add(2); System.out.println(sort(list).getClass()); //数组向下转型失败 System.out.println(((Integer[])sort2(list)).getClass()); } }
可以看到,跟我们的分析完全一致。
java中的泛型只在编译期有效,在运行时只有调用者知道它自己需要什么类型,且调用者调用泛型方法后自己做强制转换,被调用者是完全无感的,被调用者只能尽力拿到它所知道的类型。
此时,我的脑海中不经响起那熟悉的旋律,“一句话,一辈子……”,今天的这句话你记住了吗?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。