夏眠鱼

Oct 22, 2017

Java 泛型-继承和子类型

继承和子类型

正如你所知,如果类型是兼容的,就可以将一个类型的对象赋值给另一个类型的对象。例如,你可以将一个 Integer 对象赋值给 Object 对象,因为 Object 是 Integer 父类之一:

1
2
3
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // 正确

在面向对象的术语中,这被称为「是」关系。因为 Integer 是一种 Object,所以赋值是允许的。但是 Integer 也是一种 Number,所以下面的代码也是有效的:

1
2
3
4
public void someMethod(Number n) { /* ... */ }

someMethod(new Integer(10)); // 正确
someMethod(new Double(10.1)); // 正确

对于泛型来说也是如此。你可以执行一个泛型调用,将 Number 作为类型参数(Type Argument)传入,如果参数与 Number 兼容,则后续的任何调用都是允许的:

1
2
3
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // 正确
box.add(new Double(10.1)); // 正确

现在思考下面的方法:

1
public void boxTest(Box<Number> n) { /* ... */ }

上面方法接收的参数是什么类型?通过查看签名,我们可以看到它接收一个类型为 Box<Number> 的参数。但这意味着什么呢?会像你期待的那样,它允许传入 Box<Integer>Box<Double>?答案是 no,因为 Box<Integer>Box<Double> 不是 Box<Number> 的子类型。在使用泛型编程时,这是一个常见的误解,但这是一个很重要的概念。

下图中,虽然 Integer 是 Number 的子类型,但 Box<Integer> 不是 Box<Number> 的子类型:

generics-subtype-relationship

从上图我们可以得出:给定两个具体的类型 A 和 B(例如 Number 和 Integer),MyClass<A>MyClass<B> 没有半毛钱关系。不管 A 和 B 是否相关,MyClass<A>MyClass<B> 的共同父类是 Object。

当类型参数(Type Argument)相关时,如何在两个泛型之间建立子类型关系,这个我们在后续的ava 泛型-通配符会讲到。

泛型和子类型

我们可以通过继承或实现泛型来创建一个子类型的泛型类或泛型接口。一个类或接口的类型参数(Type Parameter)与另一个之间的关系由 extends 和 implements 子句来决定。

以 Collections 类为例,ArrayList<E> implements List<E>List<E> extends Collection<E>。所以 ArrayList<String>List<String> 的子类型,List<String>Collection<String> 的子类型。只要我们不改变它们的类型参数(Type Argument),就可以保留类型之间的子类型关系。

下图是一个简单的 Collections 类的层次结构:

generics-sample-hierarchy

现在假设我们想定义我们自己的 List 接口 PayloadList,它将类型参数 P 与每个元素的可选值关联起来。它的声明可能看起来像:

1
2
3
4
interface PayloadList<E, P> extends List<E> {
void setPayload(int index, P val);
...
}

以下参数化的 PayloadList 是 List<String> 的子类型

  • PayloadList<String,String>
  • PayloadList<String,Integer>
  • PayloadList<String,Exception>

它们之间的类层次结构如下:

generics-payload-List-hierarchy

OLDER > < NEWER