泛型 - 简单泛型第一部分

This commit is contained in:
Michael Liu
2019-09-02 22:03:11 +08:00
parent c289c8d880
commit e979950d4b

View File

@@ -30,9 +30,96 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如
因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。
<!-- Simple Generics -->
## 简单泛型
促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。
我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型:
```java
// generics/Holder1.java
class Automobile {}
public class Holder1 {
private Automobile a;
public Holder1(Automobile a) { this.a = a; }
Automobile get() { return a; }
}
```
这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到每个类型都编写一个新的类。
在 Java 5 之前,我们可以让这个类直接持有 `Object` 类型的对象:
```java
// generics/ObjectHolder.java
public class ObjectHolder {
private Object a;
public ObjectHolder(Object a) { this.a = a; }
public void set(Object a) { this.a = a; }
public Object get() { return a; }
public static void main(String[] args) {
ObjectHolder h2 = new ObjectHolder(new Automobile());
Automobile a = (Automobile)h2.get();
h2.set("Not an Automobile");
String s = (String)h2.get();
h2.set(1); // 自动装箱为 Integer
Integer x = (Integer)h2.get();
}
}
```
现在,`ObjectHolder` 可以持有任何类型的对象,在上面的示例中,一个 `ObjectHolder` 先后持有了三种不同类型的对象。
一个集合中存储多种不同类型的对象的情况很少见,通常而言,我们只会用集合存储同一种类型的对象。泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。
因此,与其使用 `Object` ,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。要达到这个目的,需要使用*类型参数*,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数。在下面的例子中,`T` 就是类型参数:
```java
// generics/GenericHolder.java
public class GenericHolder<T> {
private T a;
public GenericHolder() {}
public void set(T a) { this.a = a; }
public T get() { return a; }
public static void main(String[] args) {
GenericHolder<Automobile> h3 = new GenericHolder<Automobile>();
h3.set(new Automobile()); // 此处有类型校验
Automobile a = h3.get(); // 无需类型转换
//- h3.set("Not an Automobile"); // 报错
//- h3.set(1); // 报错
}
}
```
创建 `GenericHolder` 对象时,必须指明要持有的对象的类型,将其置于尖括号内,就像 `main()` 中那样使用。然后,你就只能在 `GenericHolder` 中存储该类型(或其子类,因为多态与泛型不冲突)的对象了。当你调用 `get()` 取值时,直接就是正确的类型。
这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。
你可能注意到 `h3` 的定义非常繁复。在 `=` 左边有 `GenericHolder<Automobile>`, 右边又重复了一次。在 Java 5 中,这种写法被解释成“必要的”,但在 Java 7 中设计者修正了这个问题(新的简写语法随后成为备受欢迎的特性)。以下是简写的例子:
```java
// generics/Diamond.java
class Bob {}
public class Diamond<T> {
public static void main(String[] args) {
GenericHolder<Bob> h3 = new GenericHolder<>();
h3.set(new Bob());
}
}
```
注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。
一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定类型参数即可。
<!-- Generic Interfaces -->
## 泛型接口