Java泛型上下限

布鸽不鸽 Lv4

前言

Java中支持泛型机制。泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数。

泛型信息只存在于代码编译阶段,在Java的运行期(已经生成字节码文件后)会被擦除掉,专业术语叫做类型擦除。

Java 泛型中的 extendssuper 是用来限制泛型类型参数的上限和下限的关键字。

原文地址:https://xuedongyun.cn/post/3373/

T的用法

  • 可以作用在类上
1
2
3
4
5
6
7
8
class Test<T> {
public T function(List<T> data) {
return data.get(0);
}
}

Test<String> test = new Test<>(); // 在类初始化时,就已经定死T的类型
test.function(list);
  • 也可以作用在方法上
1
2
3
4
5
6
7
8
class Test {
public <T> T function(List<T> data) {
return data.get(0);
}
}

Test test = new Test<>();
test.function(list); // 自动完成类型推断
  • 还可以使用extends限制:T类型为某类型的子类
1
2
3
public <T extends Number> void test(T number) {
System.out.println(number.intValue());
}

?的用法

  • <? extends T>:是指 “上界通配符”
  • <? super T>:是指 “下界通配符”

这两个用法令我们在使用带泛型类型的容器时,能很方便的处理容器内部泛型类型的多态问题

为什么使用

我们为什么要使用通配符和边界呢?我们来看一个例子:

假设我们的继承关系如下:

1
2
3
4
5
class Food { }

class Fruit extends Food { }

class Apple extends Fruit { }

现在我们有一个盘子类,可以用来装水果

1
2
3
4
5
6
7
8
9
10
11
class Plate<T> {
private T item;

public T getItem() {
return item;
}

public void setItem(T item) {
this.item = item;
}
}

理论上,我们有一个装水果的盘子,当然也能用来装苹果对吧?然而事实是,Java编译器会报错。即:即使容器里装的东西有继承关系,但容器之间并没有继承关系。

1
Plate<Fruit> applePlate = new Plate<Apple>(); // 编译器报错

为了让容器的泛型用起来更舒服,<? extends T><? super T>应运而生

上界(extends)

<? extends T>:表示只能存放T的子类型(包括T)

1
Plate<? extends Fruit> plate = new Plate<Apple>();

上界不能往里存,只能往外取(以父类作为类型)

1
2
3
4
5
Plate<? extends Fruit> plate = new Plate<Apple>();

plate.setItem(new Fruit()); // 不能往里存

Food item = plate.getItem(); // 可以往外取(以父类作为类型)

原因:

编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。可能是Fruit?可能是Apple?也可能是Banana?即使编译器在看到后面用Plate赋值以后,盘子里也没有被标上有“Apple”。而是标上一个占位符:CAP#1,来表示捕获一个Food或Food的子类,具体是什么类不知道。无论是想往里插入Apple或者Fruit或者Food编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。

这是为了泛型安全,因为其会在编译期间生成桥接方法<Bridge Methods>该方法中会出现强制转换,若出现多种子类型,则会强制转换失败

虽然不能添加元素,但是其中的元素都有一个共性:有相同的父类。所以可以在获取元素时,统一转化为父类取出。

下界(super)

<? super T>:表示只能存放T的父类型(包括T)

下界能往里存,但往外取只能取Object

1
2
3
4
5
6
Plate<? super Fruit> plate = new Plate<Food>();

plate.setItem(new Fruit()); // 可以存放子类
plate.setItem(new Apple());

Object item = plate.getItem(); // 只能取Object

由于存放的都是T的父类(包括T),添加元素时,只能向其添加T的子类,才能保证转化为T类型时是类型安全的。

但其中的元素类型众多,在获取元素时我们无法判断是哪一种类型,只有所有类的基类Object对象才能装下。

PECS原则

Producer Extends Consumer Super

  • 频繁往外读取内容的,适合用上界extends
  • 经常往里插入的,适合用下界super

方便容器使用

1
2
3
4
5
6
7
8
9
// 只能往外读,读取类型为父类
Plate<? extends Fruit> plate = new Plate<Apple>();
Food item = plate.getItem();

// 只能往里存,存放类型为T的子类。读取只能读为Object
Plate<? super Fruit> plate = new Plate<Food>();
plate.setItem(new Fruit());
plate.setItem(new Apple());
Object item = plate.getItem();
  • 标题: Java泛型上下限
  • 作者: 布鸽不鸽
  • 创建于 : 2023-07-16 16:58:36
  • 更新于 : 2023-07-16 17:06:55
  • 链接: https://xuedongyun.cn//post/3373/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论