前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >通配符的上下限与泛型方法

通配符的上下限与泛型方法

作者头像
用户5745563
发布2019-07-04 14:53:46
8560
发布2019-07-04 14:53:46
举报
文章被收录于专栏:码思客
java零基础入门-高级特性篇(七) 泛型 下

本章阅读有难度,请谨慎阅读,如有不适,可以跳过。

本章继续讲解泛型的上下限和其他的知识点,由于概念的复杂性,这里继续使用Book这个类来描述,使概念理解起来具备连续性。

泛型的通配符可以分为3种类型,无边界通配符,设定上限的通配符,设定下限的通配符。

上一章讲解的<?>是无边界通配符,设定上限的通配符<? extends E>,设定下限的通配符<? super E>。

设定上限的通配符

首先来看一张图。有三个类,数学书继承教科书,教科书继承书籍。现在定义一个List<? extend Book> books集合,这是什么意思呢?<? extend Book>这个泛型表示通配符?匹配的类型只能是Book类型的子类,Book类型是?类型的上限,上限就是说这里?匹配的最高类型只能是Book了。

上限

看图,如果设置通配符上限<? extend Book>,那么?可以是TextBook,也可以是MathBook,他们都是Book的子类。如果设置<? extend TextBook>,这时候通配符?的上限就是TextBook了,如果将Book类型作为通配类型,就会编译报错,而TextBook的子类MathBook作为通配类型是可以的。如果设置<? extend MathBook>,那么?只能是MathBook类型。

通配符上限

上例中主要看Student这个要读书的可怜孩子,readBook方法中设置了通配符的上限为Book,然后在主方法中设置的List泛型为MathBook,因为MathBook是Book的子类,所以满足通配符的条件,可以作为参数传递给readBook方法。这里要注意的是,设置通配符上限的时候依然不可以使用add方法。为什么?这里有点绕,绕不过来就假设。

假设通配符?是MathBook,那么参数就是List<MathBook>,但是往List<MathBook>里面添加Book类型,这是不行的,因为Book是MathBook的父类,所以设置通配符上限也不可以使用add方法。

再来看循环,在无边界通配符的时候,要变量元素只能是Object类型,但是这里可以作为Book类型遍历元素,为什么?因为通配符?已经设置了上限Book,无论?是什么类型,都是Book的子类,而子类是可以向上自动转型的,如果参数是List<MathBook>,依然可以使用Book类型来遍历MathBook元素。

设定下限的通配符

再来看设定下限的通配符。定义List<? super Book> books;使用<? super Book>的形式设置通配符的下限,意思是通配符?的类型只能是Book的父类,看图

下限

设置<? super Book>以后,如果通配符?类型为TextBook,会编译错误,因为TextBook不是Book的父类,而是子类,MathBook是孙子,错的更离谱了。如果设置<? super TextBook>,那么?是Book类型就是允许的,因为Book是TextBook的父类,其他的请自行揣摩。

通配符下限

这里只需要修改Student类型,其他代码可以保持不变。需要注意的是这里设置了下限是MathBook,而传入的参数恰好是List<MathBook>,所以这里是可以的。如果将下限设置为TextBook,代码就会报错了,因为这里的参数只能是TextBook或者父类Book,传入List<MathBook>就会发生编译错误。

这里居然可以使用add方法了,为什么?假设通配符?是Book,那么List<Book>是可以添加Book的子类MathBook类型的。因为这里通配符不论是什么类型,必须是MathBook的父类,所以在父类的List集合添加子类MathBook是完全可以的。

至于循环中又变成了Object,是因为这里无法确定父类是什么类型,无法保证父类都有getName()这个方法。因为Object是Book的父类,如果参数是List<Object>,那么就无法使用Book里的方法了,所以只能当成Object来操作。

泛型方法

泛型方法?前面不是讲了么?请注意,泛型方法需要在定义方法的时候,就对方法中的泛型类型进行定义。

非泛型方法

以上两个方法不是泛型方法,原因就是真正的泛型方法需要在方法中定义。如何定义泛型方法?

修饰符 <泛型类型参数> 返回值 方法名(){...}

请注意,在方法的修饰符与返回值之间定义泛型类型参数,这时候的方法才是一个泛型方法。泛型方法为什么要在定义方法的时候定义泛型?因为泛型是一个参数,参数就有作用域,定义在类上面的泛型作用域是整个类,定义在方法上的泛型,作用域是整个方法。

泛型方法

先看左边一张图,如果在类上面指定了泛型,而又在类中定义了泛型方法,而且泛型方法中的泛型参数和类中的泛型参数一样,那么类上的泛型类型参数会被方法中的泛型参数覆盖,程序也会出现警告。

原因就在右图,泛型类,是在实例化类的时候指明泛型的具体类型,泛型方法,是在调用方法的时候指明泛型的具体类型。就算泛型方法定义的泛型类型参数与类定义的不同也是可以的,因为方法自己定义了泛型参数,不需要类定义的泛型参数。在创建类对象的时候,具体定义的泛型类型可以和对象调用方法时,具体定义的泛型类型不同。比如Book在创建对象的时候使用的类型是Integer,而调用sayTheBookName的时候传递的参数却是String,这是完全可以的。如果定义了泛型方法,那么方法中的泛型可以看做是独立于类定义的泛型而存在的。所以如果定义泛型方法,建议方法中的泛型不要与类上定义的泛型类型相同。

然后,就算不使用泛型类,也是可以直接使用泛型方法的。比如上例中,去掉Book<T>后面的泛型定义,将T改为String,程序也不会报错,而且泛型方法可以正常被调用。

在使用泛型方法的时候有几个地方需要注意:

1)自动类型推断。比如book.sayTheBookName("教科书"),这里程序会根据传入的参数自动的将E推断为String类型。

2)在定义方法的时候,不要因为类型可以自动推断而定义相同的泛型类型参数。

相同的泛型参数

这样定义泛型方法是没有问题的,可以正确编译,也可以正确运行。但是不建议这样做,因为根据传入的参数,第一个E会被推断为String类型,而第二个E被推断为Integer类型,这样会造成理解上的歧义。

3)如果直接将泛型类型参数定义为类型是不会报错的,但是如果在集合类型的泛型中,将泛型类型定义为一样的参数,就真的会报错了。

无法推断

上面“教科书”和1很容易推断出是字符串和Integer类型,但是如果调用方法时将有泛型的集合作为参数,并且方法里面定义的集合泛型参数还是相同的,这时候程序就无法进行自动推断了。这里最好将泛型方法再多定义一个泛型参数,保证不会出现歧义,这样程序才能正确的进行类型推断。

public <M,O> int getAllNum(List<M> mathBook,List<O> englishBook){...}

这样就可以避免歧义,正确推断类型了。

泛型通配符和泛型方法

希望讲到这里你还没有晕。

那么我们继续看下一个问题。前面说的泛型通配符?可以代替任何一个类型,T这种形式的泛型类型参数不是也可以代替任何一个类型吗?他们有什么区别呢?

其实泛型方法和方法中使用通配符在某些情况下是可以相互替代的。

效果一样

1)这是他们第一个相同的地方,他们都可以接收一个未知的类型

2)你可能会说,通配符可以设置上下限啊,不好意思,这个功能泛型方法也有

泛型方法的上下限

将上面的方法修改成通配符上限和泛型方法上限也没有任何问题。需要注意的是,使用泛型方法的上下限时,需要在方法定义的时候设置上下限,而不是在参数里面设置上下限。

不同的地方在于,当设置泛型通配符上下限的时候,会存在一个只能读不能写的情况,就是无法往集合添加元素,因为不能确定类型。但是使用泛型方法的时候,就可以对集合进行添加操作,因为调用泛型方法的时候,类型就已经确定了。所以如果需要对集合元素进行读取之外的操作,可以使用泛型方法。

再一个就是当多个泛型类型参数之间有依赖关系的时候,可以使用泛型方法。

泛型的依赖

这里有2个对象,依赖对象和被依赖对象,T extends B,T是依赖对象,B是被依赖对象。如果依赖对象不确定,可以使用泛型通配符,但是如果被依赖对象不确定,则不可以使用泛型通配符。

依赖对象不确定

依赖对象使用通配符没有问题,程序可以运行。因为通配符类型的上限就是B。

被依赖对象不确定

如果被依赖对象不确定,则无法确定T类型的上限,导致程序编译出错。所以如果多个泛型类型之间有依赖关系,使用泛型方法会比较适合。

泛型的擦除

泛型类型信息只在编译的时候发挥作用,一旦被加载到虚拟机泛型信息会被全部丢弃。所以在编译阶段List<Book>和List<TextBook>可以看做两个不同的类型,但是一旦加载到虚拟机,他们就是同样的类型。泛型被丢了,那他是个什么类型?用专业的话说就是擦除到泛型的上限。比如没有指定上限的时候,擦除后的类型是Object,如果制定了类型的上限比如<? extends Book>,那么擦除后的类型就是Book。关于泛型的擦除会涉及到反射知识,这里老规矩,先混脸熟。

泛型知识一般多用于对代码进行高层次抽象,比如编写一些工具方法,框架,比如在集合框架中就有大量的泛型使用,所以有一定的难度,初学者掌握集合的泛型使用即可。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码思客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档