@@ -30,9 +30,96 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如 |
30 | 30 | 因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。 |
31 | 31 |
|
32 | 32 | <!-- Simple Generics --> |
33 | | - |
34 | 33 | ## 简单泛型 |
35 | 34 |
|
| 35 | +促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。 |
| 36 | + |
| 37 | +我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型: |
| 38 | + |
| 39 | +```java |
| 40 | +// generics/Holder1.java |
| 41 | + |
| 42 | +class Automobile {} |
| 43 | + |
| 44 | +public class Holder1 { |
| 45 | + private Automobile a; |
| 46 | + public Holder1(Automobile a) { this.a = a; } |
| 47 | + Automobile get() { return a; } |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到每个类型都编写一个新的类。 |
| 52 | + |
| 53 | +在 Java 5 之前,我们可以让这个类直接持有 `Object` 类型的对象: |
| 54 | + |
| 55 | +```java |
| 56 | +// generics/ObjectHolder.java |
| 57 | + |
| 58 | +public class ObjectHolder { |
| 59 | + private Object a; |
| 60 | + public ObjectHolder(Object a) { this.a = a; } |
| 61 | + public void set(Object a) { this.a = a; } |
| 62 | + public Object get() { return a; } |
| 63 | + public static void main(String[] args) { |
| 64 | + ObjectHolder h2 = new ObjectHolder(new Automobile()); |
| 65 | + Automobile a = (Automobile)h2.get(); |
| 66 | + h2.set("Not an Automobile"); |
| 67 | + String s = (String)h2.get(); |
| 68 | + h2.set(1); // 自动装箱为 Integer |
| 69 | + Integer x = (Integer)h2.get(); |
| 70 | + } |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +现在,`ObjectHolder` 可以持有任何类型的对象,在上面的示例中,一个 `ObjectHolder` 先后持有了三种不同类型的对象。 |
| 75 | + |
| 76 | +一个集合中存储多种不同类型的对象的情况很少见,通常而言,我们只会用集合存储同一种类型的对象。泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。 |
| 77 | + |
| 78 | +因此,与其使用 `Object` ,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。要达到这个目的,需要使用*类型参数*,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数。在下面的例子中,`T` 就是类型参数: |
| 79 | + |
| 80 | +```java |
| 81 | +// generics/GenericHolder.java |
| 82 | + |
| 83 | +public class GenericHolder<T> { |
| 84 | + private T a; |
| 85 | + public GenericHolder() {} |
| 86 | + public void set(T a) { this.a = a; } |
| 87 | + public T get() { return a; } |
| 88 | + public static void main(String[] args) { |
| 89 | + GenericHolder<Automobile> h3 = new GenericHolder<Automobile>(); |
| 90 | + h3.set(new Automobile()); // 此处有类型校验 |
| 91 | + Automobile a = h3.get(); // 无需类型转换 |
| 92 | + //- h3.set("Not an Automobile"); // 报错 |
| 93 | + //- h3.set(1); // 报错 |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +创建 `GenericHolder` 对象时,必须指明要持有的对象的类型,将其置于尖括号内,就像 `main()` 中那样使用。然后,你就只能在 `GenericHolder` 中存储该类型(或其子类,因为多态与泛型不冲突)的对象了。当你调用 `get()` 取值时,直接就是正确的类型。 |
| 99 | + |
| 100 | +这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。 |
| 101 | + |
| 102 | +你可能注意到 `h3` 的定义非常繁复。在 `=` 左边有 `GenericHolder<Automobile>`, 右边又重复了一次。在 Java 5 中,这种写法被解释成“必要的”,但在 Java 7 中设计者修正了这个问题(新的简写语法随后成为备受欢迎的特性)。以下是简写的例子: |
| 103 | + |
| 104 | +```java |
| 105 | +// generics/Diamond.java |
| 106 | + |
| 107 | +class Bob {} |
| 108 | + |
| 109 | +public class Diamond<T> { |
| 110 | + public static void main(String[] args) { |
| 111 | + GenericHolder<Bob> h3 = new GenericHolder<>(); |
| 112 | + h3.set(new Bob()); |
| 113 | + } |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。 |
| 118 | + |
| 119 | +一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定类型参数即可。 |
| 120 | + |
| 121 | + |
| 122 | + |
36 | 123 | <!-- Generic Interfaces --> |
37 | 124 |
|
38 | 125 | ## 泛型接口 |
|