泛型中常用的通配符
泛型中常用的通配符有T,E,K,V,?。本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:
- ?表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
?无界通配符
先从一个小例子看起,原文在这里。
我有一个父类 Animal 和几个子类,如狗、猫等,现在我需要一个动物的列表,我的第一个想法是像这样的:
List<Animal> listAnimals
但是老板的想法确实这样的:
List<? ``extends Animal> listAnimals
为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。
static int countLegs (List<? extends Animal > animals ) {
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
static int countLegs1 (List< Animal > animals ){
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
// 不会报错
countLegs( dogs );
// 报错
countLegs1(dogs);
}
当调用 countLegs1 时,就会飘红。
所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即),表示可以持有任何类型。像 countLegs 方法中,限定了上届,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错。而 countLegs1 就不行。
上界通配符 < ? extends E>
上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
- 如果传入的类型不是 E 或者 E 的子类,编译不成功
- 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
类型参数列表中如果有多个类型参数上限,用逗号分开
private <K extends A, E extends B> E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
上界通配符主要用于读数据
public class test2 {
/**
* 1.测试泛型的上界
*/
public static void main1(String[] args) {
//1.实例化一个盘子对象
Plate<Apple> applePlate = new Plate<>();
//2.调用盘子类中的方法
applePlate.setMessage(new Apple());
Plate<Banana> bananaPlate = new Plate<>();
bananaPlate.setMessage(new Banana());
//3.进行获取盘子的信息
func1(applePlate);
func1(bananaPlate);
}
public static void func1(Plate<? extends Fruit> tmp){
//tmp:传入的参数为Fruit的子类或者本身
Apple apple = new Apple();
//1.此时的tmp传入的不一定是apple,所以不能将传入,传入的只能是确定是tmp子类,但是这个子类不确定,因为tmp不确定。
//tmp.setMessage(apple);
//tmp.setMessage(new Apple());
//2.但是可以进行接收,因为tmp的上界已经确定,只需要拿上界进行接收就可以,此时有可能发生向上转型
Fruit fruit = tmp.getMessage();
System.out.println(fruit);
}
}
下界通配符 < ? super E>
下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object 在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
private <T> void test(List<? super T> dst, List<T> src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {
}
dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。
下界通配符主要用于写数据
public class test2 {
/**
* 2.测试泛型的下界
*/
public static void main(String[] args) {
//1.实例化一个盘子对象
Plate<Fruit> fruitPlate = new Plate<>();
//2.调用盘子类中的方法
fruitPlate.setMessage(new Apple());
Plate<Food> foodPlate = new Plate<>();
foodPlate.setMessage(new Banana());
//3.通过泛型进行获取盘子的信息
func2(fruitPlate);
func2(foodPlate);
}
public static void func2(Plate<? super Fruit> tmp){
// tmp:传入是Fruit的父类或者本身
// 1.此时调用set方法是传入的参数可以确定为tmp的子类,因为tmp的下界已经确定,此时传入下界的子类即可
Apple apple = new Apple();
tmp.setMessage(apple);
tmp.setMessage(new Apple());
tmp.setMessage(new Banana());
// 2.但是不可以接收,因为不确定是哪个父类或者是不是自己本身,只能直接输出。
// Fruit fruit = tmp.getMessage();
System.out.println(tmp.getMessage());
}
}