一.通配符的应用
实践检验真理,所以在说明通配符上下界的理解的时候,先说明下什么时候使用通配符
关于泛型类型通配符的作用是为了代替泛型类的类型实参。
当我们使用泛型类作为参数时,我们不想固定具体的泛型类型实参,而是想接收任意类型,或者某个类及其子类或超类类型作为类型实参,这个时候就需要使用通配符了,使用泛型类<?>
作为类型实参
总结来说通配符解决的问题就是:类B
是类A
子类,但是泛型类<B>
不是泛型类<A>
的子类,但是我们又想有这么个类型能同时接收泛型类<B>
和泛型类<A>
类型作为参数的问题
通配符类型
- 无边界通配符:<?>
使用无边界通配符可以让泛型接收任意类型的数据 - 上边界通配符 :<?extends 具体类型 >
使用固定上边界的通配符的泛型可以接收指定类型及其所有子类类型的数据,这里的指定类型可以是类也可以是接口 - 下边界通配符 :<? spuer 具体类型>
所有固定下边界的通配符的泛型可以接收指定类型及其所有超类类型的数据。
通配符无法同时指定上下边界
举个栗子:
我们常用的List
就是一个泛型类,以java.lang.Number
类及其子类,超类作为类型实参,具体继承关系如下:
看下下面这段代码:
private List<? extends Number> data;
private void test() {
List<Number> numbers = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
//List<Number> data = integers; //编译报错
data = numbers;
data = integers;
}
显然虽然Integer
是Number
的子类,但是List<Integer>
并不是List<Number>
的子类,所以List<Integer>
类型的变量不能直接赋值给List<Number>
的变量。这时候通配符就起到作用了List<? extends Number>
可以看做是List<Integer>
和List<Number>
的子类,它可以接收Number
类或者其子类型作为类型形参的泛型数据。
所以当我们要在一个方法或者类中接收不固定类型实参的泛型数据,可以考虑使用通配符
二.关于通配符的上下界
1. 无边界 和 上边界通配符
使用无边界和上边界通配符的泛型不能赋值(除了null),可以取值,但是只能去指定的类型及其超类类型(无边界只能取Object类型数据)
(无边界其实上边界就是Object)
以List
为例:下面这段代码当我们用List<?>
或者List<? extends Number>
add数据时发现都会编译报错。
为什么:根据上面应用的结论,List<Integer>
、List<Long>
、List<Double>
这些类型可以理解为List<?>
或者List<? extends Number>
的子类型。
这时候List<?>
或者List<? extends Number>
add
时候不知道到底要往List<Integer>
、List<Long>
、List<Double>
还是其他Number
子类型的List
中add
数据的是Integer
、Long
还是Double
类型。这么操作可能会引发类型不一致的问题,这显然和泛型的设计是相悖的。因此Java为了保证类型一致,是不允许这么操作的。但是null是所有引用类型都有元素,所有可以add
成功。
List<?> data = new ArrayList<>();
List<?> data = new ArrayList<>();
data.add(new Object()); //编译报错
data.add(10); //编译报错
data.add(null);
List<? extends Number> numbers = new ArrayList<>();
numbers.add(10); //编译报错
numbers.add(new BigDecimal(20)); //编译报错
numbers.add(null);
在看一下get
取值方法:
下面代码可以看出(忽略运行错误啊,只是为了说明泛型编译问题):get
方法是可以取到指定类型及其超类型的数据。
List<?> data = new ArrayList<>();
Object object = data.get(0);
List<? extends Number> numbers = new ArrayList<>();
Number number = numbers.get(0);
Object number2 = numbers.get(0);
2.下边界通配符
与无边界和上边界通配符相反,下边界通配符只能取Object类型的数据,但可以赋值,只要是指定类型或者其子类型都能成功赋值
还是以List
类为例,代码如下,对应到List
里面就是add
指定类型及其子类型数据时可以正常编译通过,但是get
方法不能编译通过
为什么:因为Long
、BigDecimal
、Float
都是Numer
的子类,根据之前的结论? super Number
代表可以接收指定类型及其父类型的数据,所以List<? super Number>
可以理解为List<Number>
或者List<Object>
的父类型,显然List<Number>
或者List<Object>
是可以add
Numer
的子类型数据的。但是get
的时候因为不知道具体是List<Number>
还是List<Object>或者是之间的什么类型,所以只能
get所以类型的父类型
Object`类型
List<? super Number> data = new ArrayList<>();
data.add(10);
data.add(new BigDecimal(1000));
data.add(10.07f);
data.add(new Object()) //编译报错
Object object = data.get(0);
Number number = data.get(0); //编译报错