泛型学习目录:
Java泛型-1(泛型的定义)
Java泛型-2(通配符)
Java泛型-3(实践篇-protostuff序列化与反序列化)
Java泛型-4(类型擦除后如何获取泛型参数)
1.1 泛型的通配符
在泛型代码中,称为通配符的问号(?)
表示未知类型。
通配符可用于各种情况:作为参数,字段或局部变量的类型,有时候作为返回类型。
类型 | 说明 | 用途 |
---|---|---|
extends A> | 上限有界通配符,泛型参数一般是A类型或者A子类型 | 一般用于输入参数 |
> | 无界限通配符 | |
super A> | 下限有界通配符,泛型类型一般是A类型或者A的父类型 | 一般用于输出参数 |
1.2 上限有界通配符(Upper Bounded)
You can use an upper bounded wildcard to relax the restrictions on a variable.
你可以使用上限通配符
来放宽对变量的限制。
举一个例子:
例如:假如要编写适用于List
、List
和List
的方法,你会怎么做?
- 你可能回想到,参数类型边界,即我们泛型上篇讲到的
extends
关键字。指定参数边界。
- 所以可以使用
通配符(?)
,后面跟着extends
关键字,后面跟着上限
。
(小胖友情提醒:此时extends
关键字代表的就是extends
或者implements
)。
答案:
List extends Number>
1.3 无界通配符
The unbounded wildcard type is specified using the wildcard character (?), for example, List>. This is called a list of unknown type.
使用通配符(?)
指定无界限通配符类型,例如List>
。这被称为未知类型的列表。
无界匹配符的适用范围:
无论该对象的泛型是何种类型,均允许传入,例如:允许
List
和List
传入。代码使用泛型类方法不依赖于类型参数
(T)
时。例如,Class>
经常被使用因为Class
里面的大部分方法都不依赖与T
。
还是举个例子说明吧:
public static void printList(List
请问是否能打印List
、List
等类型?
重要的事情说三遍:
-
List
不是List
的子类型; -
List
不是List
的子类型; -
List
不是List
的子类型;
对于具体的类型A
,List
是List>
的子类型。此方法中,可以输入List
、List
。
public static void printList(List > list){
for(Object elem:list)
System.out.print(elem +“”);
的System.out.println();
}
无界通配符的注意事项:插入除null以外的数据,出现编译错误。
List
和List>
是不一样的,可以将Object
或者Object
的任何子类插入到List
。但是只能在List>
中插入null
。
1.4 下限有界通配符(Lower Bounded)
a lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.
下限有界通配符将未知类型(?)
限制为该类型的特定类型或者超类类型。
使用通配符(?)
表示下限有界通配符,后跟super
关键字,后跟下限
。
super A>
注意:可以指定通配符的上限,也可以指定下限,但不能同时指定两者。
假设要编写方法,他的参数是Integer
类型,但是为了最大限度地提高灵活性,可以在方法里面输入List
、List
、List
任何保存Integer
值的方法。
可以使用List super Integer>
类型,因为List
值匹配Integer
类型的List,但是List super Integer>
匹配Integer
类型以及超类类型。
1.5 通配符和子类型
我们在上一节知道,尽管Integer
是Number
的子类,但List
不是List
的子类型,实际上,两个类型不相关,List
和List
的共同父类是List>
。
为了在这些类之间创建关系以便代码可以通过List
的元素访问List
的方法,可以使用上限有界通配符
List extends Integer> intList=new ArrayList<>();
List extends Number> numList=intList;
因为Integer
是Number
的子类型,而numList
是Number
对象的列表。所以intList(Integer对象列表)
和numList
之间存在关系。
1.6 泛型的多重边界
在泛型中,可以使用&符号来设定多重边界,指定泛型类型t必须是A和B的共有子类型,而此时t就具有所有限定的方法和属性。可以更加方便的进行判断。
比如,社会中,每个人都有多重身份,比如是员工,是乘客,是儿子,是父亲。若是在程序中对一类人进行管理:在公交车费优惠系统中,对部分人员(如工资低于2500元的上班族并且是站立的乘客)车费打八折。
public class Me implements Staff, Passenger {
@Override
public int getSalary() {
return 2000;
}
@Override
public boolean isStanding() {
return true;
}
//泛型的多重限定
public static void discount(T t) {
if (t.getSalary() < 2500 && t.isStanding()) {
System.out.println("恭喜你,车票打8折");
}
}
//工资低于2500元的员工并且站立的乘客车票打8折
public static void main(String[] args) {
discount(new Me());
}
}
interface Staff {
//工资
int getSalary();
}
//乘客
interface Passenger {
boolean isStanding();
}
注意:使用&符号设定多重边界,指定泛型类型T必须是Staff和Passenger的共有类型。
泛型通配符和泛型的多重边界的区别
为什么要说明多重边界?是因为编辑者太少使用它。使用通配符虽然可以缩小泛型边界,但是可能还需要进行泛型类型判断。故可以使用泛型的多重限定来优雅的解决泛型问题。
1.7 通配符使用指南(重点)
我们在学习了泛型编程之后,是不是不确定什么时候使用
上限有界通配符
什么时候使用下限有界通配符
?
参考代码:
//带有两个参数的复制方法
void copy(src,dest){
}
1. 一个In
变量:
"in"
变量向代码中提供数据,正如上面代码中:src
参数提供要复制的数据,因此它是in
参数。
2. 一个out
变量
"out"
输出变量以供其他地方使用,正如示例中的desc
。
3. in
&out
变量
当然,一些变量即用于"进入"又用于"输出"。
在决定是否使用通配符以及适合使用哪种类型的通配符时,可以使用in
和out
原则。下面是通配符指南:
in
变量使用extends
关键字定义带有上界有限通配符。out
变量使用super
关键字定义带有下界有限通配符。in
变量是Object
类型时,使用无界通配符。in&out
变量时,不使用通配符。
这些指南不适用于方法的返回类型。应该避免使用通配符作为返回类型,因为他会强制程序员使用代码来处理通配符。
注意的是:
一个list被定义为List extends ...>
可以被认为是只读的,但并不是一个严格的结论。
假设有两个类:
public class NaturalNumber {
private int i;
public NaturalNumber(int i) {
this.i = i;
}
}
class EvenNumber extends NaturalNumber {
public EvenNumber(int i) {
super(i);
}
}
我们可以看到List extends NaturalNumber>
在严格意义上不是只读的,因为可能无法存储或改变列表中现有的元素。
1.8 通配符的顺序
建议采用的顺序是List
、List>、List
List
1. List
List
2. List
List
可以进行诸如add、remove等操作,因为他的类型是固定的T类型,在编译期不需要任何的转型操作。 List>是只读操作的,不能进行增加、修改操作。因为编译器不知道List容纳的是什么类型的元素,也就无法校验类型是否安全。而且List>读取出来的元素都是Object类型,需要主动转型,所以常用于泛型方法的返回值。但是List>虽然无法增加,修改元素,但是可以删除元素,如执行remove、clear等方法。
List
也可以进行读写操作,但是它执行写入操作时需要向上转型,在读取操作后需要向下转型,而此时已经失去了泛型存在的意义了。