摘要:调用相当于通过使用通配符,可以传递任何类型的对象,但也是有缺点的。使用通配符,赋值传值的时候方便了,但是对泛型类中参数为泛型的方法起到了副作用。结论当使用父界限定通配符时,泛型类中返回值为泛型的方法不能使用。
数组 VS List
第一回合
数组类型为Object,可以存储任意类型的对象,List集合同样可以做到
Object[] obj = new Object[1]; List list = new ArrayList();
第二回合
数组类型可以为Integer类型,专门存储Integer类型对象,List集合同样也可以
Integer[] integer = new Integer[1]; Listlist = new ArrayList<>();
决胜局
前两局双方打成平手,最后的决胜局。先看数组代码:
Object[] obj = new Integer[2]; obj[0] = 52021; //编译期OK,运行期就挂了 obj[1] = "Full of confidence";
上面的代码在运行期会抛出异常:java.lang.ArrayStoreException: java.lang.String
List集合代码:
//编译期报错 List
最终结果,List集合更胜一筹,因为它能尽早的发现错误。
分析最后一战中数组的代码:
在编译期间,编译器不会检查这样的赋值,编译器觉得它是合理的,因为父类引用指向子类对象,没有任何问题。赋值时,将字符串存储在一个Object类型的数组中也说的过去,但在运行时,发现明明内存分配的是存储整型类型对象的格子,里面却放着字符串类型的对象,自然会报错了。
分析最后一战中List集合的代码:
由于运用了泛型,在编译期就发现了错误,避免了将问题带到运行时。
思考个问题,如果代码在编译期没有报错会发生什么?
在编译期没有报错并且在运行期会将泛型擦除,全部变为了Object类型。所以执行obj.add(new Integer(1))也是可以的。如果真是这样的话,那么泛型还有什么存在的意义呢?所以这种假设是不存在的,所以会在编译期报错。编译期报错的原因就是在使用泛型时,泛型的引用和创建两端,给出的泛型变量不相同,所以在使用泛型时,泛型的引用和创建两端,给出的泛型变量必须相同。
通配符通配符只能出现在等号左面,不能出现在new的一边。
List> list = new ArrayList
List extends Number> obj1 = new ArrayList
List super Integer> obj = new ArrayList
public void foo() { Listlist = new ArrayList (); //foo2(list); //编译期报错 foo3(list); //正常编译 } public void foo2(List
上面代码中,调用foo2方法编译期报错原因:泛型的引用和创建两端,给出的泛型变量不一致,相当于:List
public void foo() { Listlist = new ArrayList (); List strList = new ArrayList (); foo3(list); //正常编译 } public void foo3(List list) { //TODO }
现在希望将strList作为参数调用foo3方法,这时就想到了方法的重载,所以定义了一个重载的方法。public void foo3(List
定义完成后,竟然报错了,并且foo3(List
这里无法使用foo3方法重载,除非定义不同名字的方法。除了定义不同名字的方法之外,还可以使用通配符。
public void foo() { Listlist = new ArrayList (); List strList = new ArrayList (); foo3(list); foo3(strList); } public void foo3(List> list) { //TODO }
调用foo3(strList);相当于:
List> list = new ArrayList
通过使用通配符,foo3(List> list);可以传递任何类型的对象,但也是有缺点的。使用通配符,赋值/传值的时候方便了,但是对泛型类中参数为泛型的方法起到了副作用。如何理解泛型类中参数为泛型的方法起到了副作用这句话呢?结合代码来看
/** * 使用 ? 通配符 */ public void foo3(List> list) { //list.add(1); //编译期报错,起到了副作用 //list.add("hello"); //编译期报错,起到了副作用 Object o = list.get(0); //其实也是作废的,只是由于Object是所有类的父类,所以这里不会报错。 }
List定义:接口List
子界限定:? extends Number
解释:? 是 Number 类型或者是 Number 的子类型
缺点:参数为泛型的方法不能使用
public void foo4() { ListintList = new ArrayList (); List longList = new ArrayList (); foo5(intList); //Integer是Number的子类型 foo5(longList); //Long是Number的子类型 } public void foo5(List extends Number> list) { //list.add(1); //编译期报错 //list.add(2L); //编译期报错 }
分析以上代码:
foo5(intList);相当于List extends Number> list = intList;赋值操作,这是没有问题的;list.add(1);则会在编译期报错。
list定义的类型是List extends Number>,它带有泛型,而add方法的参数也是泛型类型,符合:泛型类中参数为泛型的方法起到了副作用这个结论。所以调用add编译期报错。
想想看,如果list.add(1);不报错:
foo4中调用了foo5(longList);相当于List extends Number> list = new ArrayList
结论:当使用子界限定通配符时,泛型类中参数为泛型的方法不能使用。
也许有人会问,这样做是否可以确定类型?
List extends Number> list = new ArrayList(); list.add(1); //编译期报错
很遗憾,这样也是不行的。? 仍然代表了不确定性,所以编译器压根就是将这种方式的方法的参数类型是泛型的全部废掉了。
add不能用,那赋值操作后可从中取值吗?答案是肯定的。
Number number = list.get(0);
分析以上代码:
无论返回什么值,总归都是Number类型的,这是可以确定的,所以可以用Number接收返回值为泛型的方法。当然,这里说没有问题也只能是Number类型或者是Object类型接收,别的类型是不可以的。
结论:当使用子界限定通配符时,泛型类中返回值为泛型的方法可以使用。
父界限定父界限定:? super Integer
解释:? 是Integer类型或者是Integer的父类型
缺点:返回值为泛型的方法不能使用
public void foo6() { ListnumList = new ArrayList (); List intList = new ArrayList (); foo7(numList); //Number是Integer的父类型 foo7(intList); //Integer是本身 } public void foo7(List super Integer> list) { list.add(1); }
分析以上代码:
list.add(1);不会报错,因为类型可以确定。
如果List super Integer> list的赋值是List super Integer> list = new ArrayList
如果List super Integer> list的赋值是List super Integer> list = new ArrayList
如果List super Integer> list的赋值是List super Integer> list = new ArrayList也没问题
所以只要add(1)就肯定没有问题,就是说add(1),这个实参1,符合任何一种情况。
结论:当使用父界限定通配符时,泛型类中参数为泛型的方法可以使用。
public void foo7(List super Integer> list) { list.add(1); Object obj = list.get(0); }
Object obj = list.get(0);这句话没有报错,但其实是作废的,只是由于Object是所有类的父类,所以才可以这么用。
结论:当使用父界限定通配符时,泛型类中返回值为泛型的方法不能使用。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/73385.html
摘要:泛型之上界下界通配符本教程是为编写的。这是在使用泛型编程时一个常见的误解,也是一个需要学习的重要概念。通配符使用指南学习使用泛型编程时,更令人困惑的一个方面是确定何时使用上限有界通配符以及何时使用下限有界通配符。 Java 泛型之上界下界通配符 本Java教程是为JDK 8编写的。本页描述的示例和实践没有利用后续版本中引入的改进。 泛型,继承和子类 如你所知,只要类型兼容,就可以将一种...
摘要:泛型类在类的申明时指定参数,即构成了泛型类。换句话说,泛型类可以看成普通类的工厂。的作用就是指明泛型的具体类型,而类型的变量,可以用来创建泛型类的对象。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 什么是泛型? 泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,...
阅读 3878·2021-09-27 13:36
阅读 4554·2021-09-22 15:12
阅读 3063·2021-09-13 10:29
阅读 1826·2021-09-10 10:50
阅读 2360·2021-09-03 10:43
阅读 518·2019-08-29 17:10
阅读 442·2019-08-26 13:52
阅读 3249·2019-08-23 14:37