在前两篇文章中介绍了泛型的基本用法和泛型的继承问题,本文将介绍泛型的高级用法:泛型的限制和通配符。
一、泛型的限制
我们可以使用任意的类型来指定一个泛型,这样是存在一定的弊端的。Java也提供了一种语法来限制泛型的指定类:
<T extends ClassOrInterface>
这里面ClassOrInterface可以是类或者是接口,有了这个限制之后,泛型T必须是ClassOrInterface的子类或者实现类,否则编译报错。举例如下:
例子里面我们将泛型限制为List的子类,因为HashSet是Set的子类,所以代码在第10行报错了。上面的例子是一个泛型类的例子,接下来再举一个泛型方法的例子:
可以看到在泛型方法show中将T限制为List或其子类,而String类型不是它的子类,所以第15行报错。
使用泛型限制之后除了能限制泛型的指定类之外,还有一个作用。我们考虑一个问题,那在泛型类A内部,利用泛型定义的变量T t能调用哪些方法呢?来看一看:
可以看到,T的对象t只能调用Object的方法。因为编译器根本不知道T具体是什么类型,只有在运行时,用户给什么类型才知道是什么类型。编译器唯一能确定的是,无论什么类型,都是派生自Object的,所以T肯定是Object的子类,所以T是可以调用Object的方法的。
那么对泛型做了限制之后,是否就能够调用一些特定的方法呢?
可以看到,限制泛型T为List子类之后,变量t就可以调用List里面提供的方法了。
二、通配符
在泛型机制中提供了一个类型通配符,那么为什么要引入通配符呢?这里举一个例子:
public class Test {
public static void main(String args[]) {
ArrayList<String> al1 = new ArrayList<>();
al1.add("haha");
al1.add("hehe");
ArrayList<Integer> al2 = new ArrayList<>();
al2.add(1);
al2.add(2);
new Test().printList(al1);
new Test().printList(al2);
}
public void printList(这里面些什么参数呢?) {
//打印的逻辑
}
}
这个例子中,我们在主方法里面创建了两个ArrayList并给它们添加了一些元素,现在我想通过printList方法遍历打印容器中的东西,那么方法的参数写什么呢?是ArrayList< String >还是ArrayList< Integer >呢?这里因为我们不知道调用者想遍历什么类型的ArrayList,所以ArrayList中的泛型是未知的,这种情况就可以使用通配符?了。代码如下:
public void printList(List<?> list) {
Iterator<?> it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
由这个例子我们知道了,当泛型类型不确定可以使用通配符。
同样的,对于通配符也可以加上限定,有3种情况:
1、无边界通配符:
A<?> a;
2、上限通配符:
A<? extends ClassOrInterface> a; //泛型必须是ClassOrInterface或它的子类/实现类
3、下限通配符:
A<? extends ClassOrInterface> a; //泛型必须是ClassOrInterface或它的父类
值得注意的是,通配符只能用于填充泛型变量T(也就是在声明对象的时候使用),在定义泛型时使用会报错:
看下面这个例子:
因为HashSet不是List子类所以报错,ArrayList不是List父类所以报错。