Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >通配符的上下限与泛型方法

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

作者头像
用户5745563
发布于 2019-07-04 06:53:46
发布于 2019-07-04 06:53:46
9020
举报
文章被收录于专栏:码思客码思客
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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解泛型
泛型(Generics)是Java编程语言中的一个特性,它允许在编译时提供类型检查并消除类型转换。Java中的泛型用于类、接口和方法的创建,它使得代码能够被不同的数据类型重用。
程序员朱永胜
2023/11/09
2760
Java进阶:【泛型】认识泛型,泛型方法,泛型类,泛型接口和通配符
例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
冷环渊
2021/11/29
3.5K0
Java进阶:【泛型】认识泛型,泛型方法,泛型类,泛型接口和通配符
【Java 基础篇】Java 泛型:类型安全的编程指南
在 Java 编程中,泛型是一项强大的特性,它允许您编写更通用、更安全和更灵活的代码。无论您是初学者还是有经验的 Java 开发人员,了解和掌握泛型都是非常重要的。本篇博客将从基础概念一直深入到高级应用,详细介绍 Java 泛型。
繁依Fanyi
2023/10/12
4130
【Java 基础篇】Java 泛型:类型安全的编程指南
泛型就这么简单
前言 从今天开始进入Java基础的复习,可能一个星期会有一篇的<十道简单算法>,我写博文的未必都是正确的~如果有写错的地方请大家多多包涵并指正~ 今天要复习的是泛型,泛型在Java中也是个很重要的知识点,本文主要讲解基础的概念,并不是高深的知识,如果基础好的同学可以当复习看看~ 一、什么是泛型? Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常. 泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型 参数化类型: 把类型当作
Java3y
2018/06/11
5390
泛型接口,泛型类和泛型通配符
泛型的使用位置,除了最常见的约束集合元素,还可以使用在接口,类,方法上面。最本质的原因就是为了在使用接口,类,方法的时候,可以将类型作为参数,进行类型的参数传递。这样可以使程序的编写更加的灵活,在创建对象,调用方法的时候动态的指定类型,所以泛型也可以理解为类型的参数化。
用户5745563
2019/07/04
2.4K0
泛型接口,泛型类和泛型通配符
精通Java,却不了解泛型?
在没有泛型的出现之前,我们通常是使用类型为 Object 的元素对象。比如我们可以构建一个类型为 Object 的集合,该集合能够存储任意数据类型的对象,但是我们从集合中取出元素的时候我们需要明确的知道存储每个元素的数据类型,这样才能进行元素转换,不然会出现 ClassCastException 异常。
蔡不菜丶
2020/12/10
5150
精通Java,却不了解泛型?
Java泛型是什么?
工作已有五年之久,回望过去,没有在一线城市快节奏下学习成长,只能自己不断在工作中学习进步,最近一直想写写属于自己的文章,记录学习的内容和知识点,当做一次成长。
科技新语
2024/10/28
1480
Java泛型是什么?
Java基础系列2:Java泛型
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架。
说故事的五公子
2019/11/10
5490
【java筑基】吃透泛型(一万字长文,建议收藏)
集合元素过去默认为Object类型,无法指定元素类型,编译时不检查类型,而且每次取出对象都要进行强制类型转换,泛型出现避免了这种臃肿的代码。下列代码会看到编译时不检查元素类型导致的异常。
半旧518
2022/10/26
4280
【java筑基】吃透泛型(一万字长文,建议收藏)
【Java】泛型
🍊 泛型(Generics)是Java编程语言中的一个强大的特性,它提供了 编译时类型安全检测机制,这意味着可以在编译期间检测到非法的类型。泛型的使用减少了程序中的强制类型转换和运行时错误的可能性。
IsLand1314
2024/10/21
1020
【Java】泛型
泛型类、泛型方法、类型通配符的使用
       你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
泰斗贤若如
2019/06/19
3.9K0
JavaSE 语法基础--- 泛型(基础知识问答)
java在推出泛型之前。程序员可以构建一个 元素类型为 Object 的集合,该集合可以存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道 每个元素的数据类型 ,否则很容易引发类型转换异常。
RAIN7
2022/04/08
4000
JavaSE 语法基础--- 泛型(基础知识问答)
Java泛型
在推出泛型之前,程序员通过构建元素类型为Object的集合,存储多个任意类型的数据对象;在使用该对象的过程中,程序员需要明确知道存储每个元素的数据类型(几乎不可能),否则会引发ClassCastException。
devi
2021/08/19
5010
Java泛型
Java 泛型:理解和应用
这就是泛型的概念,是 Java 后期的重大变化之一。泛型实现了参数化类型,可以适用于多种类型。泛型为 Java 的动态类型机制提供很好的补充,但是 Java 的泛型本质上是一种高级语法糖,也存在类型擦除导致的信息丢失等多种缺点,我们可以在本篇文章中深度探讨和分析。
phoenix.xiao
2023/08/28
2600
Java 泛型:理解和应用
Java 泛型
【1】解决元素存储的安全性问题。 【2】解决获取数据元素时,需要类型强转的问题。
Java架构师必看
2021/05/14
5610
Java 泛型
一文打通java泛型
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型
一个风轻云淡
2023/10/15
1830
一文打通java泛型
Java强化之泛型
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
yuanshuai
2022/08/17
3620
Java强化之泛型
【Java 基础 - 泛型机制详细解释】
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
奥耶可乐冰
2023/11/29
5460
【Java 基础 - 泛型机制详细解释】
【面试题精讲】泛型&通配符
泛型(Generics)是 Java 中的一种特性,它允许我们在定义类、接口和方法时使用类型参数。通过使用泛型,我们可以编写更加通用和可复用的代码。
程序员朱永胜
2023/10/14
3590
java中的泛型
java语言的多态性让我们可以把某些只能在运行时确定的类型在编译时使用父类或者父接口表示,这确实解决了很多问题。但有时程序员在声明某些变量时不知道它的具体父类或父接口,只能选择公共父类Object类型,这很不方便。
别团等shy哥发育
2023/02/25
2.7K0
java中的泛型
相关推荐
深入理解泛型
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文