正文
public interface Comparable
{
public int compareTo(T o);
}
做一个类似于下面这样的声明,这样就等于告诉编译器类型参数T代表的都是实现了Comparable接口的类,这样等于告诉编译器它们都至少实现了compareTo方法。
public static
> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0)
++count;
return count;
}
通配符
在了解通配符之前,我们首先必须要澄清一个概念,还是借用我们上面定义的Box类,假设我们添加一个这样的方法:
public void boxTest(Box
n) { /* ... */ }
那么现在Box
n允许接受什么类型的参数?我们是否能够传入Box
或者Box
呢?答案是否定的,虽然Integer和Double是Number的子类,但是在泛型中Box
或者Box
与Box
之间并没有任何的关系。这一点非常重要,接下来我们通过一个完整的例子来加深一下理解。
首先我们先定义几个简单的类,下面我们将用到它:
class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
下面这个例子中,我们创建了一个泛型类Reader,然后在f1()中当我们尝试Fruit f = fruitReader.readExact(apples);编译器会报错,因为List
与List
之间并没有任何的关系。
public class GenericReading {
static List
apples = Arrays.asList(new Apple());
static List
fruit = Arrays.asList(new Fruit());
static class Reader
{
T readExact(List
list) {
return list.get(0);
}
}
static void f1() {
Reader
fruitReader = new Reader
();
// Errors: List
cannot be applied to List
.
// Fruit f = fruitReader.readExact(apples);
}
public static void main(String[] args) {
f1();
}
}
但是按照我们通常的思维习惯,Apple和Fruit之间肯定是存在联系,然而编译器却无法识别,那怎么在泛型代码中解决这个问题呢?我们可以通过使用通配符来解决这个问题:
static class CovariantReader
{
T readCovariant(List extends T> list) {
return list.get(0);
}
}
static void f2() {
CovariantReader
fruitReader = new CovariantReader
();
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
f2();
}
这样就相当与告诉编译器, fruitReader的readCovariant方法接受的参数只要是满足Fruit的子类就行(包括Fruit自身),这样子类和父类之间的关系也就关联上了。
PECS原则
上面我们看到了类似 extends T>的用法,利用它我们可以从list里面get元素,那么我们可不可以往list里面add元素呢?我们来尝试一下:
public class GenericsAndCovariance {
public static void main(String[] args) {
// Wildcards allow covariance:
List extends Fruit> flist = new ArrayList
();
// Compile Error: can't add any type of object:
// flist.add(new Apple())
// flist.add(new Orange())
// flist.add(new Fruit())
// flist.add(new Object())
flist.add(null); // Legal but uninteresting
// We Know that it returns at least Fruit:
Fruit f = flist.get(0);
}
}
答案是否定,Java编译器不允许我们这样做,为什么呢?对于这个问题我们不妨从编译器的角度去考虑。因为List extends Fruit> flist它自身可以有多种含义:
List extends Fruit> flist = new ArrayList
();
List extends Fruit> flist = new ArrayList
();
List extends Fruit> flist = new ArrayList
();
-
当我们尝试add一个Apple的时候,flist可能指向new ArrayList
();
-
当我们尝试add一个Orange的时候,flist可能指向new ArrayList
();
-
当我们尝试add一个Fruit的时候,这个Fruit可以是任何类型的Fruit,而flist可能只想某种特定类型的Fruit,编译器无法识别所以会报错。
所以对于实现了 extends T>的集合类只能将它视为Producer向外提供(get)元素,而不能作为Consumer来对外获取(add)元素。
如果我们要add元素应该怎么做呢?可以使用 super T>:
public class GenericWriting {
static List
apples = new ArrayList
();
static List
fruit = new ArrayList
();
static
void writeExact(List
list, T item) {
list.add(item);
}
static void f1() {
writeExact(apples, new Apple());