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

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。关于泛型的擦除会涉及到反射知识,这里老规矩,先混脸熟。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容