目录
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类是所有类的父类,可以发生向上转型。
第二种:直接输出也是可以的。
如下图: