From e979950d4b20ccd64f441a4879dc5d59100f1852 Mon Sep 17 00:00:00 2001 From: Michael Liu Date: Mon, 2 Sep 2019 22:03:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E7=AE=80=E5=8D=95?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E7=AC=AC=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 89 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 5ff6a03..6b0f590 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -30,9 +30,96 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如 因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。 - ## 简单泛型 +促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./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 { + 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 h3 = new GenericHolder(); + h3.set(new Automobile()); // 此处有类型校验 + Automobile a = h3.get(); // 无需类型转换 + //- h3.set("Not an Automobile"); // 报错 + //- h3.set(1); // 报错 + } +} +``` + +创建 `GenericHolder` 对象时,必须指明要持有的对象的类型,将其置于尖括号内,就像 `main()` 中那样使用。然后,你就只能在 `GenericHolder` 中存储该类型(或其子类,因为多态与泛型不冲突)的对象了。当你调用 `get()` 取值时,直接就是正确的类型。 + +这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。 + +你可能注意到 `h3` 的定义非常繁复。在 `=` 左边有 `GenericHolder`, 右边又重复了一次。在 Java 5 中,这种写法被解释成“必要的”,但在 Java 7 中设计者修正了这个问题(新的简写语法随后成为备受欢迎的特性)。以下是简写的例子: + +```java +// generics/Diamond.java + +class Bob {} + +public class Diamond { + public static void main(String[] args) { + GenericHolder h3 = new GenericHolder<>(); + h3.set(new Bob()); + } +} +``` + +注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。 + +一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定类型参数即可。 + + + ## 泛型接口