泛型和通配符

目录

1. 什么是泛型

2. 泛型是如何编译的?

3. 泛型的上界

小栗子1:

小栗子2:

4. 泛型方法

5. 通配符

小栗子:

错误的做法和原因: 

6. 通配符的上界(多用来取数据)

7. 通配符的下界(多用来放数据)


1. 什么是泛型

泛型就是对类型进行参数化。

类名<T>  这就是一个泛型类,其中<T>是占位符,T是类型形参

泛型传的是类类型

泛型的作用:数据类型参数化,编译时自动进行类型检查和转换。

下面是定义的一个泛型类(MyArray<T>)以及这个泛型类的使用:

2. 泛型是如何编译的?

在编译时,所有T都会被擦除成Object,即擦除机制。当运行时,没有泛型的概念,全部是Object

下图中这些T都会被擦除成Object:

那泛型还有什么用?指定类型有啥用?

作用:根据传入的类型来自动进行类型的检查和转换。(即1. 存放元素的时候,会进行类型的检 2. 取出元素的时候,会自动发生类型转换)

比如传入Integer类型,(1)会检查传入的val是否为Integer类型,不是会编译报错(下图蓝色框框);(2)返回类型会自动转换成Integer类型(下图黄色框框部分)

流程:检查转换,编译,擦除,运行


我们看下面代码:(输出引用中存的地址)

 

我们发现,运行后,它们的类型中没有<>了。也就是说这些地方编译后直接被擦没了,<>不参与类型的组成。

编译后,左图所有T会被擦除成Object,右图红色框框部分会直接擦没。

3. 泛型的上界

类名<T extends 类型边界> 

有上界的泛型类,编译时,所有T都会被擦除成给的类型边界,不会擦除成Object。

没有上界,编译时,所有T都会被擦除成Object

如:

类名<T extends Number> —— 表示T一定是Number的子类或Number本身(Number是数值类)

类名<T extends Comparable<T>> —— 表示T一定实现了Comparable接口

小栗子1:

黄色框框的作用: 检查传入的类类型是否是Number的子类或Number本身,不是则编译报错。

小栗子2:

题目:有一个泛型类,类中有一个方法,方法作用是求数组中的最大值

分析: 

T是类型形参,接收的是类类型,即引用类型。引用类型定义的变量max不能通过>或<进行比较。需要通过实现Comparable接口,重写compareTo方法进行比较。(对应上图红色框框)

橘色框框的作用:检查传入的类类型是否实现了此接口,没有实现会编译报错。如下图:

正确做法如下: 传入的Person类型需要实现此接口

为啥Integer类型直接传入不报错?

因为Integer类自己实现了Comparable接口。

4. 泛型方法

非静态泛型方法:方法限定符 <T> 返回值类型 方法名(形参){...}

静态泛型方法:   方法限定符 static <T> 返回值类型 方法名(形参){...}

 ret 和 ret2 这两种都可以,一般省略不写(ret2形式),传的类型会联系上下代码自动推导出。

5. 通配符

?—— 通配符,用在泛型的使用中。

泛型的类型形参T接收的是类类型,比如包装类Integer,Double,或者自定义类型Person等。T是确定的类型,一旦你传了,我就定下来了。

而当我们传递的实参是泛型类型时,有没有一个东西可以接收类型形参不确定的泛型类型呢?

于是有了通配符。

小栗子:

有一个泛型类Message<T>,里面有set和get方法。在main函数中,我们调用set方法赋值。接着调用一个static方法func,传入message引用。在func方法中调用get方法。

分析:

由于func方法形参接收的是message引用,这个引用的类型是不确定的,可以是Message<Integer>,也可以是Message<String>,等。

总之,message的类型是一个参数不确定的泛型。(注意:1.接收的message的类型是泛型类型

2.这个泛型参数不确定)

所以func的形参是Message<?> message ,没错,接收参数不确定的泛型就用到了通配符(?)。

如下图:

但是static方法func中不能调用set方法,因为 通配符?接收的参数不确定,站在message角度,我已经来者不拒了,我怎么知道 ? 接收的是什么类型,而set方法需要传?类型的参数,所以传什么都不对。如下图:

通配符一般在使用的时候会给边界,否则它不能匹配到任何类型。给边界了,就可控了。  


错误的做法和原因: 

如果不使用通配符,就是上述做法。咦?不应该发生方法重载吗?怎么会报错?

我们来看一下报错信息:

两种方法具有相同的擦除效果。哦,原来是这样。

因为编译后,<>直接会被擦没,<>不参与类型的组成。所以不构成重载,而是变成了两个一模一样的方法。因此,就报错啦。

6. 通配符的上界(多用来取数据)

<? extends 上界> —— 表示通配符?可以接收的实参类型是“上界”的子类或者“上界”本身。

如:

<? extends Number> —— 表示通配符?可以接收的实参类型是Number的子类或者Number本身

我们一般使用通配符的上界来取数据不能放入数据。

如下:

可以取数据(绿色框框),不能放数据(红色框框)

可以取数据:可以用Fruit类型的引用接收 取的数据 原因:? 接收的要么是Fruit类,要么是它的子类。是子类就会发生向上转型。所以可以接收。

不能放数据:因为通配符?接收的类型不能确定,tmp可能是plate1(Apple),也可能是plate2(Banana),set方法的参数T类型不能确定,所以无法放数据。

7. 通配符的下界(多用来放数据)

<? super 下界> —— 表示通配符?可以接收的实参类型是“下界”的父类或者“下界”本身。

如:

<? super Fruit> —— 表示通配符?可以接收的实参类型是Fruit的父类或者Fruit本身

我们一般使用通配符的下界来放(下界的子类或本身)的数据不取数据。

如下:

  

放数据:可以放Fruit的子类或本身(绿色框框),不能放Fruit的父类(蓝色框框)

因为<? super Fruit>表示 ?只能是Fruit的父类或本身。虽然通配符?类型不确定,但可以确定要么是Fruit,要么是Fruit的父类。所以放Fruit子类类型的数据会自动发生向上转型。虽然因为 T 的类型不确定,所以向上转型成什么类型是不确定的,但能发生向上转型就够了。证明可以放Fruit的子类或本身。

而Fruit父类类型的数据照样不能放。因为类型不确定,根本不知道?接收的是哪个类型。所以放父类数据是不合适的。

不能取数据

因为?要么是Fruit的父类,要么是Fruit本身。所以get方法返回值 T 的类型不确定,也就是说,我们无法知道取出的数据类型是什么,那么,我们用什么接收都不妥。因为没有给上界呀。无法发生向上转型。

但说实话,取数据接收其实也是可以的,

第一种:我们可以用Object接收,因为Object类是所有类的父类,可以发生向上转型。

第二种:直接输出也是可以的。

如下图:

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值