From 93578121b3501268d1f56bdf6eadd3311f7ff3aa Mon Sep 17 00:00:00 2001 From: xiangflight Date: Wed, 4 Dec 2019 10:06:29 +0800 Subject: [PATCH] =?UTF-8?q?revision[20]=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 2862 ++++++++++++++++++++------------------ 1 file changed, 1491 insertions(+), 1371 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index d14c2cc..2c089a3 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -34,7 +34,7 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如 ## 简单泛型 -促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。 +促成泛型出现的最主要的动机之一是为了创建*集合类*,参见 [集合](book/12-Collections.md) 章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。 我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型: @@ -44,9 +44,9 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如 class Automobile {} public class Holder1 { - private Automobile a; - public Holder1(Automobile a) { this.a = a; } - Automobile get() { return a; } + private Automobile a; + public Holder1(Automobile a) { this.a = a; } + Automobile get() { return a; } } ``` @@ -58,18 +58,19 @@ public class Holder1 { // 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(); - } + 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(); + } } ``` @@ -83,17 +84,18 @@ public class ObjectHolder { // 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); // 报错 - } + 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); // 报错 + } } ``` @@ -109,10 +111,10 @@ public class GenericHolder { class Bob {} public class Diamond { - public static void main(String[] args) { - GenericHolder h3 = new GenericHolder<>(); - h3.set(new Bob()); - } + public static void main(String[] args) { + GenericHolder h3 = new GenericHolder<>(); + h3.set(new Bob()); + } } ``` @@ -133,14 +135,15 @@ public class Diamond { package onjava; public class Tuple2 { - public final A a1; - public final B a2; - public Tuple2(A a, B b) { a1 = a; a2 = b; } - public String rep() { return a1 + ", " + a2; } - @Override - public String toString() { - return "(" + rep() + ")"; - } + public final A a1; + public final B a2; + public Tuple2(A a, B b) { a1 = a; a2 = b; } + public String rep() { return a1 + ", " + a2; } + + @Override + public String toString() { + return "(" + rep() + ")"; + } } ``` @@ -157,49 +160,50 @@ public class Tuple2 { package onjava; public class Tuple3 extends Tuple2 { - public final C a3; - public Tuple3(A a, B b, C c) { - super(a, b); - a3 = c; - } - @Override - public String rep() { - return super.rep() + ", " + a3; - } + public final C a3; + public Tuple3(A a, B b, C c) { + super(a, b); + a3 = c; + } + + @Override + public String rep() { + return super.rep() + ", " + a3; + } } - // onjava/Tuple4.java package onjava; public class Tuple4 extends Tuple3 { - public final D a4; - public Tuple4(A a, B b, C c, D d) { - super(a, b, c); - a4 = d; - } - @Override - public String rep() { - return super.rep() + ", " + a4; - } + public final D a4; + public Tuple4(A a, B b, C c, D d) { + super(a, b, c); + a4 = d; + } + + @Override + public String rep() { + return super.rep() + ", " + a4; + } } - // onjava/Tuple5.java package onjava; public class Tuple5 extends Tuple4 { - public final E a5; - public Tuple5(A a, B b, C c, D d, E e) { - super(a, b, c, d); - a5 = e; - } - @Override - public String rep() { - return super.rep() + ", " + a5; - } + public final E a5; + public Tuple5(A a, B b, C c, D d, E e) { + super(a, b, c, d); + a5 = e; + } + + @Override + public String rep() { + return super.rep() + ", " + a5; + } } ``` @@ -220,31 +224,31 @@ public class Vehicle {} import onjava.*; public class TupleTest { - static Tuple2 f() { - // 47 自动装箱为 Integer - return new Tuple2<>("hi", 47); - } + static Tuple2 f() { + // 47 自动装箱为 Integer + return new Tuple2<>("hi", 47); + } - static Tuple3 g() { - return new Tuple3<>(new Amphibian(), "hi", 47); - } + static Tuple3 g() { + return new Tuple3<>(new Amphibian(), "hi", 47); + } - static Tuple4 h() { - return new Tuple4<>(new Vehicle(), new Amphibian(), "hi", 47); - } + static Tuple4 h() { + return new Tuple4<>(new Vehicle(), new Amphibian(), "hi", 47); + } - static Tuple5 k() { - return new Tuple5<>(new Vehicle(), new Amphibian(), "hi", 47, 11.1); - } + static Tuple5 k() { + return new Tuple5<>(new Vehicle(), new Amphibian(), "hi", 47, 11.1); + } - public static void main(String[] args) { - Tuple2 ttsi = f(); - System.out.println(ttsi); - // ttsi.a1 = "there"; // 编译错误,因为 final 不能重新赋值 - System.out.println(g()); - System.out.println(h()); - System.out.println(k()); - } + public static void main(String[] args) { + Tuple2 ttsi = f(); + System.out.println(ttsi); + // ttsi.a1 = "there"; // 编译错误,因为 final 不能重新赋值 + System.out.println(g()); + System.out.println(h()); + System.out.println(k()); + } } /* 输出: @@ -263,7 +267,7 @@ public class TupleTest { ### 一个堆栈类 -接下来我们看一个稍微复杂一点的例子:堆栈。在[集合](./12-Collections.md)一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。 +接下来我们看一个稍微复杂一点的例子:堆栈。在 [集合](book/12-Collections.md) 一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。 这次我们不用 `LinkedList` 来实现自己的内部链式存储机制。 @@ -272,45 +276,46 @@ public class TupleTest { // 用链式结构实现的堆栈 public class LinkedStack { - private static class Node { - U item; - Node next; + private static class Node { + U item; + Node next; - Node() { item = null; next = null; } - Node(U item, Node next) { - this.item = item; - this.next = next; - } + Node() { item = null; next = null; } + + Node(U item, Node next) { + this.item = item; + this.next = next; + } - boolean end() { - return item == null && next == null; + boolean end() { + return item == null && next == null; + } } - } - private Node top = new Node<>(); // 栈顶 + private Node top = new Node<>(); // 栈顶 - public void push(T item) { - top = new Node<>(item, top); - } - - public T pop() { - T result = top.item; - if (!top.end()) { - top = top.next; + public void push(T item) { + top = new Node<>(item, top); } - return result; - } - public static void main(String[] args) { - LinkedStack lss = new LinkedStack<>(); - for (String s : "Phasers on stun!".split(" ")) { - lss.push(s); + public T pop() { + T result = top.item; + if (!top.end()) { + top = top.next; + } + return result; } - String s; - while ((s = lss.pop()) != null) { - System.out.println(s); + + public static void main(String[] args) { + LinkedStack lss = new LinkedStack<>(); + for (String s : "Phasers on stun!".split(" ")) { + lss.push(s); + } + String s; + while ((s = lss.pop()) != null) { + System.out.println(s); + } } - } } ``` @@ -336,19 +341,18 @@ import java.util.*; import java.util.stream.*; public class RandomList extends ArrayList { - private Random rand = new Random(47); + private Random rand = new Random(47); - public T select() { - return get(rand.nextInt(size())); - } + public T select() { + return get(rand.nextInt(size())); + } - public static void main(String[] args) { - RandomList rs = new RandomList<>(); - Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add); - IntStream.range(0, 11).forEach(i -> - System.out.print(rs.select() + " ")); - ); - } + public static void main(String[] args) { + RandomList rs = new RandomList<>(); + Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add); + IntStream.range(0, 11).forEach(i -> + System.out.print(rs.select() + " ")); + } } ``` @@ -375,13 +379,13 @@ brown over fox quick quick dog brown The brown lazy brown package generics.coffee; public class Coffee { - private static long counter = 0; - private final long id = counter++; + private static long counter = 0; + private final long id = counter++; - @Override - public String toString() { - return getClass().getSimpleName() + " " + id; - } + @Override + public String toString() { + return getClass().getSimpleName() + " " + id; + } } @@ -422,52 +426,52 @@ import java.util.stream.*; public class CoffeeSupplier implements Supplier, Iterable { - private Class[] types = { Latte.class, Mocha.class, - Cappuccino.class, Americano.class, Breve.class }; - private static Random rand = new Random(47); + private Class[] types = { Latte.class, Mocha.class, + Cappuccino.class, Americano.class, Breve.class }; + private static Random rand = new Random(47); - public CoffeeSupplier() {} - // For iteration: - private int size = 0; - public CoffeeSupplier(int sz) { size = sz; } + public CoffeeSupplier() {} + // For iteration: + private int size = 0; + public CoffeeSupplier(int sz) { size = sz; } - @Override - public Coffee get() { - try { - return (Coffee) types[rand.nextInt(types.length)].newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - class CoffeeIterator implements Iterator { - int count = size; @Override - public boolean hasNext() { return count > 0; } - @Override - public Coffee next() { - count--; - return CoffeeSupplier.this.get(); + public Coffee get() { + try { + return (Coffee) types[rand.nextInt(types.length)].newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - @Override - public Iterator iterator() { - return new CoffeeIterator(); - } - - public static void main(String[] args) { - Stream.generate(new CoffeeSupplier()) - .limit(5) - .forEach(System.out::println); - for (Coffee c : new CoffeeSupplier(5)) { - System.out.println(c); + class CoffeeIterator implements Iterator { + int count = size; + @Override + public boolean hasNext() { return count > 0; } + @Override + public Coffee next() { + count--; + return CoffeeSupplier.this.get(); + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + @Override + public Iterator iterator() { + return new CoffeeIterator(); + } + + public static void main(String[] args) { + Stream.generate(new CoffeeSupplier()) + .limit(5) + .forEach(System.out::println); + for (Coffee c : new CoffeeSupplier(5)) { + System.out.println(c); + } } - } } ``` @@ -497,21 +501,21 @@ import java.util.function.*; import java.util.stream.*; public class Fibonacci implements Supplier { - private int count = 0; - @Override - public Integer get() { return fib(count++); } + private int count = 0; + @Override + public Integer get() { return fib(count++); } - private int fib(int n) { - if(n < 2) return 1; - return fib(n-2) + fib(n-1); - } + private int fib(int n) { + if(n < 2) return 1; + return fib(n-2) + fib(n-1); + } - public static void main(String[] args) { - Stream.generate(new Fibonacci()) - .limit(18) - .map(n -> n + " ") - .forEach(System.out::print); - } + public static void main(String[] args) { + Stream.generate(new Fibonacci()) + .limit(18) + .map(n -> n + " ") + .forEach(System.out::print); + } } ``` @@ -534,30 +538,30 @@ import java.util.*; public class IterableFibonacci extends Fibonacci implements Iterable { - private int n; - public IterableFibonacci(int count) { n = count; } + private int n; + public IterableFibonacci(int count) { n = count; } - @Override - public Iterator iterator() { - return new Iterator() { - @Override - public boolean hasNext() { return n > 0; } - @Override - public Integer next() { - n--; - return IterableFibonacci.this.get(); - } - @Override - public void remove() { // Not implemented - throw new UnsupportedOperationException(); - } - }; - } + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { return n > 0; } + @Override + public Integer next() { + n--; + return IterableFibonacci.this.get(); + } + @Override + public void remove() { // Not implemented + throw new UnsupportedOperationException(); + } + }; + } - public static void main(String[] args) { - for(int i : new IterableFibonacci(18)) - System.out.print(i + " "); - } + public static void main(String[] args) { + for(int i : new IterableFibonacci(18)) + System.out.print(i + " "); + } } ``` @@ -898,7 +902,7 @@ public enum Watercolors { } ``` -为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](./22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: +为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](book/22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: ```java // generics/WatercolorSets.java @@ -1086,7 +1090,7 @@ Serializable] */ ``` -在[第十二章 集合](./12-Collections.md)的[本章小结](./12-Collections.md#本章小结)部分将会用到这里的输出结果。 +在第十二章 [集合的本章小节](book/12-Collections.md#本章小结) 部分将会用到这里的输出结果。 @@ -2571,7 +2575,7 @@ public class EpicBattle { ## 通配符 -你已经在 [集合](./12-Collections.md) 章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号,在 [类型信息](./19-Type-Information.md) 一章中这种示例更多。本节将更深入地探讨这个特性。 +你已经在 [集合](book/12-Collections.md) 章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号,在 [类型信息](book/19-Type-Information.md) 一章中这种示例更多。本节将更深入地探讨这个特性。 我们的起始示例要展示数组的一种特殊行为:你可以将派生类的数组赋值给基类的引用: @@ -2754,11 +2758,11 @@ false 但是,`equals()` 方法可以正常工作,因为它接受的参数是 **Object** 而不是 **T** 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。 -Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode()` 方法变得更加容易,当然还有很多其他功能。`equals()` 方法的标准形式参考 [附录:理解 equals 和 hashCode 方法](./Appendix-Understanding-equals-and-hashCode) 一章。 +Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode()` 方法变得更加容易,当然还有很多其他功能。`equals()` 方法的标准形式参考 [附录:理解 equals 和 hashCode 方法](book/Appendix-Understanding-equals-and-hashCode) 一章。 ### 逆变 -还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 `<?super MyClass>` ,甚至或者使用类型参数: `<?super T>`(尽管你不能对泛型参数给出一个超类型边界;即不能声明 `` )。这使得你可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向 **Collection** 写入了: +还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 `<?super MyClass>` ,或者甚至使用类型参数: `<?super T>`(尽管你不能对泛型参数给出一个超类型边界;即不能声明 `` )。这使得你可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向 **Collection** 写入了: ```java // generics/SuperTypeWildcards.java @@ -2772,123 +2776,135 @@ public class SuperTypeWildcards { } ``` -参数 **Apple** 是 **Apple** 的某种基类型的 **List**,这样你就知道向其中添加 **Apple** 或 **Apple** 的子类型是安全的。但是,既然 **Apple** 是下界,那么你可以知道向这样的 **List** 中添加 **Fruit** 是不安全的,因为这将使这个 **List** 敞开口子,从而可以向其中添加非 **Apple** 类型的对象,而这是违反静态类型安全的。 -因此你可能会根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界。 -超类型边界放松了在可以向方法传递的参数上所作的限制,此示例提供了逆变和通配符的概述:: +参数 **apples** 是 **Apple** 的某种基类型的 **List**,这样你就知道向其中添加 **Apple** 或 **Apple** 的子类型是安全的。但是因为 **Apple** 是下界,所以你知道向这样的 **List** 中添加 **Fruit** 是不安全的,因为这将使这个 **List** 敞开口子,从而可以向其中添加非 **Apple** 类型的对象,而这是违反静态类型安全的。 +下面的示例复习了一下逆变和通配符的的使用: ```java // generics/GenericReading.java import java.util.*; public class GenericReading { - static List apples = - Arrays.asList(new Apple()); - static List fruit = Arrays.asList(new Fruit()); - static T readExact(List list) { - return list.get(0); - } - // A static method adapts to each call: - static void f1() { - Apple a = readExact(apples); - Fruit f = readExact(fruit); - f = readExact(apples); - } - // A class type is established - // when the class is instantiated: - static class Reader { - T readExact(List list) { return list.get(0); } - } - static void f2() { - Reader fruitReader = new Reader<>(); - Fruit f = fruitReader.readExact(fruit); - //- Fruit a = fruitReader.readExact(apples); - // error: incompatible types: List - // cannot be converted to List - } - static class CovariantReader { - T readCovariant(List list) { - return list.get(0); + static List apples = Arrays.asList(new Apple()); + static List fruit = Arrays.asList(new Fruit()); + + static T readExact(List list) { + return list.get(0); + } + + // A static method adapts to each call: + static void f1() { + Apple a = readExact(apples); + Fruit f = readExact(fruit); + f = readExact(apples); + } + + // A class type is established + // when the class is instantiated: + static class Reader { + T readExact(List list) { + return list.get(0); + } + } + + static void f2() { + Reader fruitReader = new Reader<>(); + Fruit f = fruitReader.readExact(fruit); + //- Fruit a = fruitReader.readExact(apples); + // error: incompatible types: List + // cannot be converted to List + } + + static class CovariantReader { + T readCovariant(List list) { + return list.get(0); + } + } + + static void f3() { + CovariantReader fruitReader = new CovariantReader<>(); + Fruit f = fruitReader.readCovariant(fruit); + Fruit a = fruitReader.readCovariant(apples); + } + + public static void main(String[] args) { + f1(); + f2(); + f3(); } - } - static void f3() { - CovariantReader fruitReader = - new CovariantReader<>(); - Fruit f = fruitReader.readCovariant(fruit); - Fruit a = fruitReader.readCovariant(apples); - } - public static void main(String[] args) { - f1(); f2(); f3(); - } } ``` -第一个方法 `readExact()` 使用了精确的类型。因此如果使用这个没有任何通配符的精确类型,就可以向 **List** 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 `readExact()` 可以有效地“适应”每个方法调用,并能够从 `List` 中返回一个 **Apple** ,从 `List` 中返回一个 **Fruit** ,就像在 `f1()` 中看到的那样。因此,如果可以摆脱静态泛型方法,那么当只是读取时,就不需要协变类型了。 -但是,如果有一个泛型类,那么当你创建这个类的实例时,要为这个类确定参数。就像在 `f2()` 中看到的,**fruitReader** 实例可以从 `List` 中读取一个 **Fruit** ,因为这就是它的确切类型。但是 `List` 还应该产生 **Fruit** 对象,而 **fruitReader** 不允许这么做。 -为了修正这个问题,`CovariantReader.readCovcariant()` 方法将接受 `List<?extendsT>` ,因此,从这个列表中读取一个 **T** 是安全的(你知道在这个列表中的所有对象至少是一个 **T** ,并且可能是从T导出的某种对象)。在 `f3()` 中,你可以看到现在可以从 `List` 中读取 **Fruit** 了。 +`readExact()` 方法使用了精确的类型。如果使用这个没有任何通配符的精确类型,就可以向 **List** 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 `readExact()` 可以有效地“适应”每个方法调用,并能够从 `List` 中返回一个 **Apple** ,从 `List` 中返回一个 **Fruit** ,就像在 `f1()` 中看到的那样。因此,如果可以摆脱静态泛型方法,那么在读取时就不需要协变类型了。 +然而对于泛型类来说,当你创建这个类的实例时,就要为这个类确定参数。就像在 `f2()` 中看到的,**fruitReader** 实例可以从 `List` 中读取一个 **Fruit** ,因为这就是它的确切类型。但是 `List` 也应该产生 **Fruit** 对象,而 **fruitReader** 不允许这么做。 +为了修正这个问题,`CovariantReader.readCovariant()` 方法将接受 `List<?extends T>` ,因此,从这个列表中读取一个 **T** 是安全的(你知道在这个列表中的所有对象至少是一个 **T** ,并且可能是从 T 导出的某种对象)。在 `f3()` 中,你可以看到现在可以从 `List` 中读取 **Fruit** 了。 ### 无界通配符 -无界通配符<?>看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的: +无界通配符 `` 看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的: ```java // generics/UnboundedWildcards1.java import java.util.*; public class UnboundedWildcards1 { - static List list1; - static List list2; - static List list3; - static void assign1(List list) { - list1 = list; - list2 = list; - //- list3 = list; - // warning: [unchecked] unchecked conversion - // list3 = list; - // ^ - // required: List - // found: List - } - static void assign2(List list) { - list1 = list; - list2 = list; - list3 = list; - } - static void assign3(List list) { - list1 = list; - list2 = list; - list3 = list; - } - public static void main(String[] args) { - assign1(new ArrayList()); - assign2(new ArrayList()); - //- assign3(new ArrayList()); - // warning: [unchecked] unchecked method invocation: - // method assign3 in class UnboundedWildcards1 - // is applied to given types - // assign3(new ArrayList()); - // ^ - // required: List - // found: ArrayList - // warning: [unchecked] unchecked conversion - // assign3(new ArrayList()); - // ^ - // required: List - // found: ArrayList - // 2 warnings - assign1(new ArrayList<>()); - assign2(new ArrayList<>()); - assign3(new ArrayList<>()); - // Both forms are acceptable as List: - List wildList = new ArrayList(); - wildList = new ArrayList<>(); - assign1(wildList); - assign2(wildList); - assign3(wildList); - } + static List list1; + static List list2; + static List list3; + + static void assign1(List list) { + list1 = list; + list2 = list; + //- list3 = list; + // warning: [unchecked] unchecked conversion + // list3 = list; + // ^ + // required: List + // found: List + } + + static void assign2(List list) { + list1 = list; + list2 = list; + list3 = list; + } + + static void assign3(List list) { + list1 = list; + list2 = list; + list3 = list; + } + + public static void main(String[] args) { + assign1(new ArrayList()); + assign2(new ArrayList()); + //- assign3(new ArrayList()); + // warning: [unchecked] unchecked method invocation: + // method assign3 in class UnboundedWildcards1 + // is applied to given types + // assign3(new ArrayList()); + // ^ + // required: List + // found: ArrayList + // warning: [unchecked] unchecked conversion + // assign3(new ArrayList()); + // ^ + // required: List + // found: ArrayList + // 2 warnings + assign1(new ArrayList<>()); + assign2(new ArrayList<>()); + assign3(new ArrayList<>()); + // Both forms are acceptable as List: + List wildList = new ArrayList(); + wildList = new ArrayList<>(); + assign1(wildList); + assign2(wildList); + assign3(wildList); + } } ``` -有很多情况都和你在这里看到的情况类似,即编译器很少关心使用的是原生类型还是 `` 。在这些情况中,`` 可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上,它是在声明:“我是想用Java的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。” +有很多情况都和你在这里看到的情况类似,即编译器很少关心使用的是原生类型还是 `` 。在这些情况中,`` 可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上它是在声明:“我是想用 Java 的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。” 第二个示例展示了无界通配符的一个重要应用。当你在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要: ```java @@ -2896,334 +2912,345 @@ public class UnboundedWildcards1 { import java.util.*; public class UnboundedWildcards2 { - static Map map1; - static Map map2; - static Map map3; - static void assign1(Map map) { map1 = map; } - static void assign2(Map map) { map2 = map; } - static void assign3(Map map) { map3 = map; } - public static void main(String[] args) { - assign1(new HashMap()); - assign2(new HashMap()); - //- assign3(new HashMap()); - // warning: [unchecked] unchecked method invocation: - // method assign3 in class UnboundedWildcards2 - // is applied to given types - // assign3(new HashMap()); - // ^ - // required: Map - // found: HashMap - // warning: [unchecked] unchecked conversion - // assign3(new HashMap()); - // ^ - // required: Map - // found: HashMap - // 2 warnings - assign1(new HashMap<>()); - assign2(new HashMap<>()); - assign3(new HashMap<>()); - } + static Map map1; + static Map map2; + static Map map3; + + static void assign1(Map map) { + map1 = map; + } + + static void assign2(Map map) { + map2 = map; + } + + static void assign3(Map map) { + map3 = map; + } + + public static void main(String[] args) { + assign1(new HashMap()); + assign2(new HashMap()); + //- assign3(new HashMap()); + // warning: [unchecked] unchecked method invocation: + // method assign3 in class UnboundedWildcards2 + // is applied to given types + // assign3(new HashMap()); + // ^ + // required: Map + // found: HashMap + // warning: [unchecked] unchecked conversion + // assign3(new HashMap()); + // ^ + // required: Map + // found: HashMap + // 2 warnings + assign1(new HashMap<>()); + assign2(new HashMap<>()); + assign3(new HashMap<>()); + } } ``` -但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards.java** 展示了编译器处理 `List` 和 `List` 时是不同的。 -令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。因为,事实上,由于泛型参数将擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List<?>` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道那种类型是什么。” -编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式: -作为原生类型,具有具体的类型参数以及具有无界通配符参数: +但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards1.java** 展示了编译器处理 `List` 和 `List` 是不同的。 +令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道类型是什么。” +编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式:作为原生类型,具有具体的类型参数以及具有无界通配符参数: ```java // generics/Wildcards.java // Exploring the meaning of wildcards public class Wildcards { - // Raw argument: - static void rawArgs(Holder holder, Object arg) { - //- holder.set(arg); - // warning: [unchecked] unchecked call to set(T) - // as a member of the raw type Holder - // holder.set(arg); - // ^ - // where T is a type-variable: - // T extends Object declared in class Holder - // 1 warning + // Raw argument: + static void rawArgs(Holder holder, Object arg) { + //- holder.set(arg); + // warning: [unchecked] unchecked call to set(T) + // as a member of the raw type Holder + // holder.set(arg); + // ^ + // where T is a type-variable: + // T extends Object declared in class Holder + // 1 warning - // Can't do this; don't have any 'T': - // T t = holder.get(); + // Can't do this; don't have any 'T': + // T t = holder.get(); - // OK, but type information is lost: - Object obj = holder.get(); - } - // Like rawArgs(), but errors instead of warnings: - static void - unboundedArg(Holder holder, Object arg) { - //- holder.set(arg); - // error: method set in class Holder - // cannot be applied to given types; - // holder.set(arg); - // ^ - // required: CAP#1 - // found: Object - // reason: argument mismatch; - // Object cannot be converted to CAP#1 - // where T is a type-variable: - // T extends Object declared in class Holder - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object from capture of ? - // 1 error + // OK, but type information is lost: + Object obj = holder.get(); + } + + // Like rawArgs(), but errors instead of warnings: + static void unboundedArg(Holder holder, Object arg) { + //- holder.set(arg); + // error: method set in class Holder + // cannot be applied to given types; + // holder.set(arg); + // ^ + // required: CAP#1 + // found: Object + // reason: argument mismatch; + // Object cannot be converted to CAP#1 + // where T is a type-variable: + // T extends Object declared in class Holder + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error - // Can't do this; don't have any 'T': - // T t = holder.get(); + // Can't do this; don't have any 'T': + // T t = holder.get(); - // OK, but type information is lost: - Object obj = holder.get(); - } - static T exact1(Holder holder) { - return holder.get(); - } - static T exact2(Holder holder, T arg) { - holder.set(arg); - return holder.get(); - } - static - T wildSubtype(Holder holder, T arg) { - //- holder.set(arg); - // error: method set in class Holder - // cannot be applied to given types; - // holder.set(arg); - // ^ - // required: CAP#1 - // found: T#1 - // reason: argument mismatch; - // T#1 cannot be converted to CAP#1 - // where T#1,T#2 are type-variables: - // T#1 extends Object declared in method - // wildSubtype(Holder,T#1) - // T#2 extends Object declared in class Holder - // where CAP#1 is a fresh type-variable: - // CAP#1 extends T#1 from - // capture of ? extends T#1 - // 1 error + // OK, but type information is lost: + Object obj = holder.get(); + } + + static T exact1(Holder holder) { + return holder.get(); + } + + static T exact2(Holder holder, T arg) { + holder.set(arg); + return holder.get(); + } + + static T wildSubtype(Holder holder, T arg) { + //- holder.set(arg); + // error: method set in class Holder + // cannot be applied to given types; + // holder.set(arg); + // ^ + // required: CAP#1 + // found: T#1 + // reason: argument mismatch; + // T#1 cannot be converted to CAP#1 + // where T#1,T#2 are type-variables: + // T#1 extends Object declared in method + // wildSubtype(Holder,T#1) + // T#2 extends Object declared in class Holder + // where CAP#1 is a fresh type-variable: + // CAP#1 extends T#1 from + // capture of ? extends T#1 + // 1 error + return holder.get(); + } + + static void wildSupertype(Holder holder, T arg) { + holder.set(arg); + //- T t = holder.get(); + // error: incompatible types: + // CAP#1 cannot be converted to T + // T t = holder.get(); + // ^ + // where T is a type-variable: + // T extends Object declared in method + // wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object super: + // T from capture of ? super T + // 1 error - return holder.get(); - } - static - void wildSupertype(Holder holder, T arg) { - holder.set(arg); - //- T t = holder.get(); - // error: incompatible types: - // CAP#1 cannot be converted to T - // T t = holder.get(); - // ^ - // where T is a type-variable: - // T extends Object declared in method - // wildSupertype(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object super: - // T from capture of ? super T - // 1 error + // OK, but type information is lost: + Object obj = holder.get(); + } + + public static void main(String[] args) { + Holder raw = new Holder<>(); + // Or: + raw = new Holder(); + Holder qualified = new Holder<>(); + Holder unbounded = new Holder<>(); + Holder bounded = new Holder<>(); + Long lng = 1L; - // OK, but type information is lost: - Object obj = holder.get(); - } - public static void main(String[] args) { - Holder raw = new Holder<>(); - // Or: - raw = new Holder(); - Holder qualified = new Holder<>(); - Holder unbounded = new Holder<>(); - Holder bounded = new Holder<>(); - Long lng = 1L; + rawArgs(raw, lng); + rawArgs(qualified, lng); + rawArgs(unbounded, lng); + rawArgs(bounded, lng); - rawArgs(raw, lng); - rawArgs(qualified, lng); - rawArgs(unbounded, lng); - rawArgs(bounded, lng); + unboundedArg(raw, lng); + unboundedArg(qualified, lng); + unboundedArg(unbounded, lng); + unboundedArg(bounded, lng); - unboundedArg(raw, lng); - unboundedArg(qualified, lng); - unboundedArg(unbounded, lng); - unboundedArg(bounded, lng); + //- Object r1 = exact1(raw); + // warning: [unchecked] unchecked method invocation: + // method exact1 in class Wildcards is applied + // to given types + // Object r1 = exact1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact1(Holder) + // warning: [unchecked] unchecked conversion + // Object r1 = exact1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact1(Holder) + // 2 warnings - //- Object r1 = exact1(raw); - // warning: [unchecked] unchecked method invocation: - // method exact1 in class Wildcards is applied - // to given types - // Object r1 = exact1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method exact1(Holder) - // warning: [unchecked] unchecked conversion - // Object r1 = exact1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method exact1(Holder) - // 2 warnings + Long r2 = exact1(qualified); + Object r3 = exact1(unbounded); // Must return Object + Long r4 = exact1(bounded); - Long r2 = exact1(qualified); - Object r3 = exact1(unbounded); // Must return Object - Long r4 = exact1(bounded); + //- Long r5 = exact2(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method exact2 in class Wildcards is + // applied to given types + // Long r5 = exact2(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // warning: [unchecked] unchecked conversion + // Long r5 = exact2(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // 2 warnings - //- Long r5 = exact2(raw, lng); - // warning: [unchecked] unchecked method invocation: - // method exact2 in class Wildcards is - // applied to given types - // Long r5 = exact2(raw, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // warning: [unchecked] unchecked conversion - // Long r5 = exact2(raw, lng); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // 2 warnings + Long r6 = exact2(qualified, lng); - Long r6 = exact2(qualified, lng); + //- Long r7 = exact2(unbounded, lng); + // error: method exact2 in class Wildcards + // cannot be applied to given types; + // Long r7 = exact2(unbounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: inference variable T has + // incompatible bounds + // equality constraints: CAP#1 + // lower bounds: Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error - //- Long r7 = exact2(unbounded, lng); - // error: method exact2 in class Wildcards - // cannot be applied to given types; - // Long r7 = exact2(unbounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: inference variable T has - // incompatible bounds - // equality constraints: CAP#1 - // lower bounds: Long - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object from capture of ? - // 1 error + //- Long r8 = exact2(bounded, lng); + // error: method exact2 in class Wildcards + // cannot be applied to given types; + // Long r8 = exact2(bounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: inference variable T + // has incompatible bounds + // equality constraints: CAP#1 + // lower bounds: Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Long from + // capture of ? extends Long + // 1 error - //- Long r8 = exact2(bounded, lng); - // error: method exact2 in class Wildcards - // cannot be applied to given types; - // Long r8 = exact2(bounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: inference variable T - // has incompatible bounds - // equality constraints: CAP#1 - // lower bounds: Long - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Long from - // capture of ? extends Long - // 1 error + //- Long r9 = wildSubtype(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method wildSubtype in class Wildcards + // is applied to given types + // Long r9 = wildSubtype(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method wildSubtype(Holder,T) + // warning: [unchecked] unchecked conversion + // Long r9 = wildSubtype(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method wildSubtype(Holder,T) + // 2 warnings - //- Long r9 = wildSubtype(raw, lng); - // warning: [unchecked] unchecked method invocation: - // method wildSubtype in class Wildcards - // is applied to given types - // Long r9 = wildSubtype(raw, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // where T is a type-variable: - // T extends Object declared in - // method wildSubtype(Holder,T) - // warning: [unchecked] unchecked conversion - // Long r9 = wildSubtype(raw, lng); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method wildSubtype(Holder,T) - // 2 warnings + Long r10 = wildSubtype(qualified, lng); + // OK, but can only return Object: + Object r11 = wildSubtype(unbounded, lng); + Long r12 = wildSubtype(bounded, lng); - Long r10 = wildSubtype(qualified, lng); - // OK, but can only return Object: - Object r11 = wildSubtype(unbounded, lng); - Long r12 = wildSubtype(bounded, lng); + //- wildSupertype(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method wildSupertype in class Wildcards + // is applied to given types + // wildSupertype(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // warning: [unchecked] unchecked conversion + // wildSupertype(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // 2 warnings - //- wildSupertype(raw, lng); - // warning: [unchecked] unchecked method invocation: - // method wildSupertype in class Wildcards - // is applied to given types - // wildSupertype(raw, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // warning: [unchecked] unchecked conversion - // wildSupertype(raw, lng); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // 2 warnings + wildSupertype(qualified, lng); - wildSupertype(qualified, lng); + //- wildSupertype(unbounded, lng); + // error: method wildSupertype in class Wildcards + // cannot be applied to given types; + // wildSupertype(unbounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: cannot infer type-variable(s) T + // (argument mismatch; Holder + // cannot be converted to Holder) + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error - //- wildSupertype(unbounded, lng); - // error: method wildSupertype in class Wildcards - // cannot be applied to given types; - // wildSupertype(unbounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: cannot infer type-variable(s) T - // (argument mismatch; Holder - // cannot be converted to Holder) - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object from capture of ? - // 1 error - - //- wildSupertype(bounded, lng); - // error: method wildSupertype in class Wildcards - // cannot be applied to given types; - // wildSupertype(bounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: cannot infer type-variable(s) T - // (argument mismatch; Holder - // cannot be converted to Holder) - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Long from capture of - // ? extends Long - // 1 error - } + //- wildSupertype(bounded, lng); + // error: method wildSupertype in class Wildcards + // cannot be applied to given types; + // wildSupertype(bounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: cannot infer type-variable(s) T + // (argument mismatch; Holder + // cannot be converted to Holder) + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Long from capture of + // ? extends Long + // 1 error + } } ``` - - -在 `rawArgs()` 中,编译器知道 `Holder` 是一个泛型类型,因此即使它在这里被表示成一个原生类型,编译器仍旧知道向 `set()` 传递一个 **Object** 是不安全的。由于它是原生类型,你可以将任何类型的对象传递给 `set()` ,而这个对象将被向上转型为 **Object** 。因此,无论何时;只要使用了原生类型,都会放弃编译期检查。对 `get()` 的调用说明了相同的问题:没有任何 **T** 类型的对象,因此结果只能是一个 **Object** 。 +在 `rawArgs()` 中,编译器知道 `Holder` 是一个泛型类型,因此即使它在这里被表示成一个原生类型,编译器仍旧知道向 `set()` 传递一个 **Object** 是不安全的。由于它是原生类型,你可以将任何类型的对象传递给 `set()` ,而这个对象将被向上转型为 **Object** 。因此无论何时,只要使用了原生类型,都会放弃编译期检查。对 `get()` 的调用说明了相同的问题:没有任何 **T** 类型的对象,因此结果只能是一个 **Object**。 人们很自然地会开始考虑原生 `Holder` 与 `Holder` 是大致相同的事物。但是 `unboundedArg()` 强调它们是不同的——它揭示了相同的问题,但是它将这些问题作为错误而不是警告报告,因为原生 **Holder** 将持有任何类型的组合,而 `Holder` 将持有具有某种具体类型的同构集合,因此不能只是向其中传递 **Object** 。 在 `exact1()` 和 `exact2()` 中,你可以看到使用了确切的泛型参数——没有任何通配符。你将看到,`exact2()`与 `exact1()` 具有不同的限制,因为它有额外的参数。 -在 `wildSubtype()` 中,在 **Holder** 类型上的限制被放松为包括持有任何扩展自 **T** 的对象的 **Holder** 。这还是意味着如果T是 **Fruit** ,那么 `holder` 可以是 `Holder` ,这是合法的。为了防止将 **Orange** 放置到 `Holder` 中,对 `set()` 的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍旧知道任何来自 `Holder<?extends Fruit>` 的对象至少是 **Fruit** ,因此 `get()` (或者任何将产生具有这个类型参数的返回值的方法)都是允许的。 -`wildSupertype()` 展示了超类型通配符,这个方法展示了与 `wildSubtype()` 相反的行为:`holder` 可以是持有任何T的基类型的容器。因此, `set()` 可以接受**T** ,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是 **T** )。但是,尝试着调用 `get()` 是没有用的,因为由 `holder` 持有的类型可以是任何超类型,因此唯一安全的类型就是 **Object** 。 -这个示例还展示了对于在 `unbounded()` 中使用无界通配符能够做什么不能做什么所做出的限制。对于迁移兼容性,`rawArgs()` 将接受所有 **Holder** 的不同变体,而不会产生警告。`unboundedArg()` 方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的方式并不相同。 +在 `wildSubtype()` 中,在 **Holder** 类型上的限制被放松为包括持有任何扩展自 **T** 的对象的 **Holder** 。这还是意味着如果 T 是 **Fruit** ,那么 `holder` 可以是 `Holder` ,这是合法的。为了防止将 **Orange** 放置到 `Holder` 中,对 `set()` 的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍旧知道任何来自 `Holder<?extends Fruit>` 的对象至少是 **Fruit** ,因此 `get()` (或者任何将产生具有这个类型参数的返回值的方法)都是允许的。 +`wildSupertype()` 展示了超类型通配符,这个方法展示了与 `wildSubtype()` 相反的行为:`holder` 可以是持有任何 T 的基类型的容器。因此, `set()` 可以接受 **T** ,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是 **T** )。但是,尝试着调用 `get()` 是没有用的,因为由 `holder` 持有的类型可以是任何超类型,因此唯一安全的类型就是 **Object** 。 +这个示例还展示了对于在 `unbounded()` 中使用无界通配符能够做什么不能做什么所做出的限制:因为你没有 **T**,所以你不能将 `set()` 或 `get()` 作用于 **T** 上。 + +在 `main()` 方法中你看到了某些方法在接受某些类型的参数时没有报错和警告。为了迁移兼容性,`rawArgs()` 将接受所有 **Holder** 的不同变体,而不会产生警告。`unboundedArg()` 方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的方式并不相同。 如果向接受“确切”泛型类型(没有通配符)的方法传递一个原生 **Holder** 引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息。如果向 `exact1()` 传递一个无界引用,就不会有任何可以确定返回类型的类型信息。 可以看到,`exact2()` 具有最多的限制,因为它希望精确地得到一个 `Holder` ,以及一个具有类型 **T** 的参数,正由于此,它将产生错误或警告,除非提供确切的参数。有时,这样做很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想要从泛型参数中返回类型确定的返回值(就像在 `wildSubtype()` 中看到的那样),或者是否想要向泛型参数传递类型确定的参数(就像在 `wildSupertype()` 中看到的那样)。 @@ -3231,61 +3258,61 @@ public class Wildcards { ### 捕获转换 -有一种情况特别需要使用 ``而不是原生类型。如果向一个使用 `` 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 `@SuppressWarnings` 注解被移除之后才能起作用: +有一种特殊情况需要使用 `` 而不是原生类型。如果向一个使用 `` 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 `@SuppressWarnings` 注解被移除之后才能起作用: ```java // generics/CaptureConversion.java public class CaptureConversion { - static void f1(Holder holder) { - T t = holder.get(); - System.out.println(t.getClass().getSimpleName()); - } - static void f2(Holder holder) { - f1(holder); // Call with captured type - } - @SuppressWarnings("unchecked") - public static void main(String[] args) { - Holder raw = new Holder<>(1); - - f1(raw); - // warning: [unchecked] unchecked method invocation: - // method f1 in class CaptureConversion - // is applied to given types - // f1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method f1(Holder) - // warning: [unchecked] unchecked conversion - // f1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method f1(Holder) - // 2 warnings - - f2(raw); // No warnings - Holder rawBasic = new Holder(); - - rawBasic.set(new Object()); - // warning: [unchecked] unchecked call to set(T) - // as a member of the raw type Holder - // rawBasic.set(new Object()); - // ^ - // where T is a type-variable: - // T extends Object declared in class Holder - // 1 warning - - f2(rawBasic); // No warnings - // Upcast to Holder, still figures it out: - Holder wildcarded = new Holder<>(1.0); - f2(wildcarded); - } + static void f1(Holder holder) { + T t = holder.get(); + System.out.println(t.getClass().getSimpleName()); + } + + static void f2(Holder holder) { + f1(holder); // Call with captured type + } + + @SuppressWarnings("unchecked") + public static void main(String[] args) { + Holder raw = new Holder<>(1); + f1(raw); + // warning: [unchecked] unchecked method invocation: + // method f1 in class CaptureConversion + // is applied to given types + // f1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method f1(Holder) + // warning: [unchecked] unchecked conversion + // f1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method f1(Holder) + // 2 warnings + f2(raw); // No warnings + + Holder rawBasic = new Holder(); + rawBasic.set(new Object()); + // warning: [unchecked] unchecked call to set(T) + // as a member of the raw type Holder + // rawBasic.set(new Object()); + // ^ + // where T is a type-variable: + // T extends Object declared in class Holder + // 1 warning + f2(rawBasic); // No warnings + + // Upcast to Holder, still figures it out: + Holder wildcarded = new Holder<>(1.0); + f2(wildcarded); + } } /* Output: Integer @@ -3295,17 +3322,19 @@ Double */ ``` -`f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中,`f1()` 被调用,而 `f1()` 需要一个已知参数。这里所发生的是:参数类型在调用 `f2()` 的过程中被捕获,因此它可以在对 `f1()` 的调用中被使用。 -你可能想知道,这项技术是否可以用于写入,但是这要求要在传递 `Holder`时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()`中返回 **T**,因为 **T ** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。 +`f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中调用了 `f1()`,而 `f1()` 需要一个已知参数。这里所发生的是:在调用 `f2()` 的过程中捕获了参数类型,并在调用 `f1()` 时使用了这种类型。 +你可能想知道这项技术是否可以用于写入,但是这要求在传递 `Holder` 时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()` 中返回 **T**,因为 **T ** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。 + + ## 问题 -本节将阐述在使用Java泛型时会出现的各类问题。 +本节将阐述在使用 Java 泛型时会出现的各类问题。 ### 任何基本类型都不能作为类型参数 -正如本章早先提到过的,你将在Java泛型中发现的限制之一是,不能将基本类型用作类型参数。因此,不能创建 `ArrayList` 之类的东西。 -解决之道是使用基本类型的包装器类以及JavaSE5的自动包装机制。如果创建一个 `ArrayList`,并将基本类型 **int** 应用于这个容器,那么你将发现自动包装机制将自动地实现 **int** 到 **Integer** 的双向转换——因此,这几乎就像是有一个 `ArrayList`一样: +正如本章早先提到的,Java 泛型的限制之一是不能将基本类型用作类型参数。因此,不能创建 `ArrayList` 之类的东西。 +解决方法是使用基本类型的包装器类以及自动装箱机制。如果创建一个 `ArrayList`,并将基本类型 **int** 应用于这个集合,那么你将发现自动装箱机制将自动地实现 **int** 到 **Integer** 的双向转换——因此,这几乎就像是有一个 `ArrayList` 一样: ```java // generics/ListOfInt.java @@ -3315,39 +3344,37 @@ import java.util.*; import java.util.stream.*; public class ListOfInt { - public static void main(String[] args) { - List li = IntStream.range(38, 48) - .boxed() // Converts ints to Integers - .collect(Collectors.toList()); - System.out.println(li); - } + public static void main(String[] args) { + List li = IntStream.range(38, 48) + .boxed() // Converts ints to Integers + .collect(Collectors.toList()); + System.out.println(li); + } } /* Output: [38, 39, 40, 41, 42, 43, 44, 45, 46, 47] */ ``` -注意,自动包装机制甚至允许用foreach语法来产生 **int** 。 -通常,这种解决方案工作得很好——能够成功地存储和读取 **int** ,有一些转换碰巧在发生的同时会对你屏蔽掉。但是,如果性能成为了问题,就需要使用专门适配基本类型的容器版。**Org.apache.commons.collections.primitives** 就是一种开源的这类版本。 -下面是另外一种方式,它可以创建持有 **Byte** 的 **Set** : +通常,这种解决方案工作得很好——能够成功地存储和读取 **int**,自动装箱隐藏了转换的过程。但是如果性能成为问题的话,就需要使用专门为基本类型适配的特殊版本的集合;一个开源版本的实现是 **org.apache.commons.collections.primitives**。 +下面是另外一种方式,它可以创建持有 **Byte** 的 **Set**: ```java // generics/ByteSet.java import java.util.*; public class ByteSet { - Byte[] possibles = { 1,2,3,4,5,6,7,8,9 }; - Set mySet = - new HashSet<>(Arrays.asList(possibles)); - // But you can't do this: - // Set mySet2 = new HashSet<>( - // Arrays.asList(1,2,3,4,5,6,7,8,9)); + Byte[] possibles = { 1,2,3,4,5,6,7,8,9 }; + Set mySet = new HashSet<>(Arrays.asList(possibles)); + // But you can't do this: + // Set mySet2 = new HashSet<>( + // Arrays.asList(1,2,3,4,5,6,7,8,9)); } ``` -注意,自动包装机制解决了一些问题,但并不是解决了所有问题。 +自动装箱机制解决了一些问题,但并没有解决所有问题。 -在下面的示例中,**FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的) **Supplier** 实现来自“数组”一章,并且在 `main()` 中,可以看到 `FillArray.fill()` 使用它在数组中填充对象: +在下面的示例中,**FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的)。**Supplier** 实现来自 [数组](book/21-Arrays.md) 一章,并且在 `main()` 中,可以看到 `FillArray.fill()` 使用对象填充了数组: ```java // generics/PrimitiveGenericTest.java @@ -3357,33 +3384,36 @@ import java.util.function.*; // Fill an array using a generator: interface FillArray { - static T[] fill(T[] a, Supplier gen) { - Arrays.setAll(a, n -> gen.get()); - return a; - } - static int[] fill(int[] a, IntSupplier gen) { - Arrays.setAll(a, n -> gen.getAsInt()); - return a; - } - static long[] fill(long[] a, LongSupplier gen) { - Arrays.setAll(a, n -> gen.getAsLong()); - return a; - } - static double[] fill(double[] a, DoubleSupplier gen) { - Arrays.setAll(a, n -> gen.getAsDouble()); - return a; - } + static T[] fill(T[] a, Supplier gen) { + Arrays.setAll(a, n -> gen.get()); + return a; + } + + static int[] fill(int[] a, IntSupplier gen) { + Arrays.setAll(a, n -> gen.getAsInt()); + return a; + } + + static long[] fill(long[] a, LongSupplier gen) { + Arrays.setAll(a, n -> gen.getAsLong()); + return a; + } + + static double[] fill(double[] a, DoubleSupplier gen) { + Arrays.setAll(a, n -> gen.getAsDouble()); + return a; + } } public class PrimitiveGenericTest { - public static void main(String[] args) { - String[] strings = FillArray.fill( - new String[5], new Rand.String(9)); - System.out.println(Arrays.toString(strings)); - int[] integers = FillArray.fill( - new int[9], new Rand.Pint()); - System.out.println(Arrays.toString(integers)); - } + public static void main(String[] args) { + String[] strings = FillArray.fill( + new String[5], new Rand.String(9)); + System.out.println(Arrays.toString(strings)); + int[] integers = FillArray.fill( + new int[9], new Rand.Pint()); + System.out.println(Arrays.toString(integers)); + } } /* Output: [btpenpccu, xszgvgmei, nneeloztd, vewcippcy, gpoalkljl] @@ -3391,7 +3421,7 @@ public class PrimitiveGenericTest { */ ``` -自动装箱不适用于数组,因此我们必须创建 `FillArray.fill()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll()` 有用,因为它返回填充的数组。 +自动装箱不适用于数组,因此我们必须创建 `FillArray.fill()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll()` 有用一点,因为它返回填充的数组。 ### 实现参数化接口 @@ -3406,17 +3436,16 @@ interface Payable {} class Employee implements Payable {} -class Hourly extends Employee -implements Payable {} +class Hourly extends Employee implements Payable {} ``` **Hourly** 不能编译,因为擦除会将 `Payable` 和 `Payable` 简化为相同的类 **Payable**,这样,上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是,如果从 **Payable** 的两种用法中都移除掉泛型参数(就像编译器在擦除阶段所做的那样)这段代码就可以编译。 -在使用某些更基本的 Java 接口,例如 `Comparable` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后就会看到的那样。 +在使用某些更基本的 Java 接口,例如 `Comparable` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后看到的那样。 ### 转型和警告 -使用带有泛型类型参数的转型或 **instanceof** 不会有任何效果。下面的容器在内部将各个值存储为 **Object**,并在获取这些值时,再将它们转型回 **T**: +使用带有泛型类型参数的转型或 **instanceof** 不会有任何效果。下面的集合在内部将各个值存储为 **Object**,并在获取这些值时,再将它们转型回 **T**: ```java // generics/GenericCast.java @@ -3424,40 +3453,44 @@ import java.util.*; import java.util.stream.*; class FixedSizeStack { - private final int size; - private Object[] storage; - private int index = 0; - FixedSizeStack(int size) { - this.size = size; - storage = new Object[size]; - } - public void push(T item) { - if(index < size) - storage[index++] = item; - } - @SuppressWarnings("unchecked") - public T pop() { - return index == 0 ? null : (T)storage[--index]; - } - @SuppressWarnings("unchecked") - Stream stream() { - return (Stream)Arrays.stream(storage); - } + private final int size; + private Object[] storage; + private int index = 0; + + FixedSizeStack(int size) { + this.size = size; + storage = new Object[size]; + } + + public void push(T item) { + if(index < size) + storage[index++] = item; + } + + @SuppressWarnings("unchecked") + public T pop() { + return index == 0 ? null : (T)storage[--index]; + } + + @SuppressWarnings("unchecked") + Stream stream() { + return (Stream)Arrays.stream(storage); + } } public class GenericCast { - static String[] letters = - "ABCDEFGHIJKLMNOPQRS".split(""); - public static void main(String[] args) { - FixedSizeStack strings = - new FixedSizeStack<>(letters.length); - Arrays.stream("ABCDEFGHIJKLMNOPQRS".split("")) - .forEach(strings::push); - System.out.println(strings.pop()); - strings.stream() - .map(s -> s + " ") - .forEach(System.out::print); - } + static String[] letters = "ABCDEFGHIJKLMNOPQRS".split(""); + + public static void main(String[] args) { + FixedSizeStack strings = + new FixedSizeStack<>(letters.length); + Arrays.stream("ABCDEFGHIJKLMNOPQRS".split("")) + .forEach(strings::push); + System.out.println(strings.pop()); + strings.stream() + .map(s -> s + " ") + .forEach(System.out::print); + } } /* Output: S @@ -3466,7 +3499,7 @@ A B C D E F G H I J K L M N O P Q R S ``` 如果没有 **@SuppressWarnings** 注解,编译器将对 `pop()` 产生 “unchecked cast” 警告。由于擦除的原因,编译器无法知道这个转型是否是安全的,并且 `pop()` 方法实际上并没有执行任何转型。 -这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop()` 实际上只是将 **Object** 转型为 **Object**。 +这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop()` 实际上只是将 **Object** 转型为 **Object**。 有时,泛型没有消除对转型的需要,这就会由编译器产生警告,而这个警告是不恰当的。例如: ```java @@ -3475,16 +3508,16 @@ import java.io.*; import java.util.*; public class NeedCasting { - @SuppressWarnings("unchecked") - public void f(String[] args) throws Exception { - ObjectInputStream in = new ObjectInputStream( - new FileInputStream(args[0])); - List shapes = (List)in.readObject(); - } + @SuppressWarnings("unchecked") + public void f(String[] args) throws Exception { + ObjectInputStream in = new ObjectInputStream( + new FileInputStream(args[0])); + List shapes = (List)in.readObject(); + } } ``` -正如你将在附件:对象序列化( Appendix: Object Serialization)中学到的那样,`readObject() `无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解,并编译这个程序时,就会得到下面的警告。 +正如你将在 [附录:对象序列化](book/Appendix-Object-Serialization.md) 中学到的那样,`readObject()` 无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解并编译这个程序时,就会得到下面的警告。 ``` NeedCasting.java uses unchecked or unsafe operations. @@ -3500,7 +3533,7 @@ NeedCasting.java:10: warning: [unchecked] unchecked cast 1 warning ``` -你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用在 Java SE5 中引入的新的转型形式,既通过泛型类来转型: +你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用 Java 5 引入的新的转型形式,既通过泛型类来转型: ```java // generics/ClassCasting.java @@ -3508,15 +3541,15 @@ import java.io.*; import java.util.*; public class ClassCasting { - @SuppressWarnings("unchecked") - public void f(String[] args) throws Exception { - ObjectInputStream in = new ObjectInputStream( - new FileInputStream(args[0])); - // Won't Compile: -// List lw1 = -// List<>.class.cast(in.readObject()); - List lw2 = List.class.cast(in.readObject()); - } + @SuppressWarnings("unchecked") + public void f(String[] args) throws Exception { + ObjectInputStream in = new ObjectInputStream( + new FileInputStream(args[0])); + // Won't Compile: + // List lw1 = + // List<>.class.cast(in.readObject()); + List lw2 = List.class.cast(in.readObject()); + } } ``` @@ -3536,7 +3569,7 @@ List.class.cast(in.readobject()) ### 重载 -下面的程序是不能编译的,即使编译它是一种合理的尝试: +下面的程序是不能编译的,即使它看起来是合理的: ```java // generics/UseList.java @@ -3544,29 +3577,101 @@ List.class.cast(in.readobject()) import java.util.*; public class UseList { - void f(List v) {} - void f(List v) {} + void f(List v) {} + void f(List v) {} } ``` +因为擦除,所以重载方法产生了的类型签名。 + +因而,当擦除后的参数不能产生唯一的参数列表时,你必须提供不同的方法名: + +```java +// generics/UseList2.java + +import java.util.*; + +public class UseList2 { + void f1(List v) {} + void f2(List v) {} +} +``` + +幸运的是,编译器可以检测到这类问题。 + +### 基类劫持接口 + +假设你有一个实现了 **Comparable** 接口的 **Pet** 类: + +```java +// generics/ComparablePet.java + +public class ComparablePet implements Comparable { + @Override + public int compareTo(ComparablePet o) { + return 0; + } +} +``` + +尝试缩小 **ComparablePet** 子类的比较类型是有意义的。例如,**Cat** 类可以与其他的 **Cat** 比较: + +```java +// generics/HijackedInterface.java +// {WillNotCompile} + +class Cat extends ComparablePet implements Comparable { + // error: Comparable cannot be inherited with + // different arguments: and + // class Cat + // ^ + // 1 error + public int compareTo(Cat arg) { + return 0; + } +} +``` + +不幸的是,这不能工作。一旦 **Comparable** 的类型参数设置为 **ComparablePet**,其他的实现类只能比较 **ComparablePet**: + +```java +// generics/RestrictedComparablePets.java + +public class Hamster extends ComparablePet implements Comparable { + + @Override + public int compareTo(ComparablePet arg) { + return 0; + } +} +// Or just: +class Gecko extends ComparablePet { + public int compareTo(ComparablePet arg) { + return 0; + } +} +``` + +**Hamster** 显示了重新实现 **ComparableSet** 中相同的接口是可能的,只要接口完全相同,包括参数类型。然而正如 **Gecko** 中所示,这与直接覆写基类的方法完全相同。 + ## 自限定的类型 - +在 Java 泛型中,有一个似乎经常性出现的惯用法,它相当令人费解: -在Java泛型中,有一个好像是经常性出现的惯用法,它相当令人费解: - -``` +```java class SelfBounded> { // ... ``` -这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而T由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。 +这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而 **T** 由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。 + 当你首次看到它时,很难去解析它,它强调的是当 **extends** 关键字用于边界与用来创建子类明显是不同的。 ### 古怪的循环泛型 为了理解自限定类型的含义,我们从这个惯用法的一个简单版本入手,它没有自限定的边界。 + 不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类。也就是说,可以声明: ```java @@ -3579,23 +3684,22 @@ public class CuriouslyRecurringGeneric ``` 这可以按照 Jim Coplien 在 C++ 中的*古怪的循环模版模式*的命名方式,称为古怪的循环泛型(CRG)。“古怪的循环”是指类相当古怪地出现在它自己的基类中这一事实。 -为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,甚至那些将被擦除为 **Object** 的类型。下面是表示了这种情况的一个泛型类: +为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java 中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,尽管这些将被擦除为 **Object** 的类型。下面是表示了这种情况的一个泛型类: ```java // generics/BasicHolder.java public class BasicHolder { - T element; - void set(T arg) { element = arg; } - T get() { return element; } - void f() { - System.out.println( - element.getClass().getSimpleName()); - } + T element; + void set(T arg) { element = arg; } + T get() { return element; } + void f() { + System.out.println(element.getClass().getSimpleName()); + } } ``` -这是一个普通的泛型类型,它的一些方法将接受和产生具有其参数类型的对象,还有一个方法将在其存储的域上执行操作(尽管只是在这个域上执行 **Object** 操作)。 +这是一个普通的泛型类型,它的一些方法将接受和产生具有其参数类型的对象,还有一个方法在其存储的域上执行操作(尽管只是在这个域上执行 **Object** 操作)。 我们可以在一个古怪的循环泛型中使用 **BasicHolder**: ```java @@ -3604,25 +3708,23 @@ public class BasicHolder { class Subtype extends BasicHolder {} public class CRGWithBasicHolder { - public static void main(String[] args) { - Subtype - st1 = new Subtype(), - st2 = new Subtype(); - st1.set(st2); - Subtype st3 = st1.get(); - st1.f(); - } + public static void main(String[] args) { + Subtype st1 = new Subtype(), st2 = new Subtype(); + st1.set(st2); + Subtype st3 = st1.get(); + st1.f(); + } } /* Output: Subtype */ ``` -注意,这里有些东西很重要:新类 **Subtype** 接受的参数和返回的值具有 **Subtype** 类型而不仅仅是基类 **BasicHolder** 类型。这就是 CRG 的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在**Subtype** 中,传递给 `set()` 的参数和从 `get()` 返回的类型都是确切的 **Subtype** 。 +注意,这里有些东西很重要:新类 **Subtype** 接受的参数和返回的值具有 **Subtype** 类型而不仅仅是基类 **BasicHolder** 类型。这就是 CRG 的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在**Subtype** 中,传递给 `set()` 的参数和从 `get()` 返回的类型都是确切的 **Subtype**。 ### 自限定 -BasicHolder可以使用任何类型作为其泛型参数,就像下面看到的那样: +**BasicHolder** 可以使用任何类型作为其泛型参数,就像下面看到的那样: ```java // generics/Unconstrained.java @@ -3634,38 +3736,41 @@ class Other {} class BasicOther extends BasicHolder {} public class Unconstrained { - public static void main(String[] args) { - BasicOther b = new BasicOther(); - BasicOther b2 = new BasicOther(); - b.set(new Other()); - Other other = b.get(); - b.f(); - } + public static void main(String[] args) { + BasicOther b = new BasicOther(); + BasicOther b2 = new BasicOther(); + b.set(new Other()); + Other other = b.get(); + b.f(); + } } /* Output: Other */ ``` -限定将采取额外的步骤,强制泛型当作其自己的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用: +限定将采取额外的步骤,强制泛型当作其自身的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用: ```java // generics/SelfBounding.java class SelfBounded> { - T element; - SelfBounded set(T arg) { - element = arg; - return this; - } - T get() { return element; } + T element; + SelfBounded set(T arg) { + element = arg; + return this; + } + T get() { return element; } } class A extends SelfBounded {} class B extends SelfBounded {} // Also OK class C extends SelfBounded { - C setAndGet(C arg) { set(arg); return get(); } + C setAndGet(C arg) { + set(arg); + return get(); + } } class D {} @@ -3678,14 +3783,14 @@ class D {} class F extends SelfBounded {} public class SelfBounding { - public static void main(String[] args) { - A a = new A(); - a.set(new A()); - a = a.set(new A()).get(); - a = a.get(); - C c = new C(); - c = c.setAndGet(new C()); - } + public static void main(String[] args) { + A a = new A(); + a.set(new A()); + a = a.set(new A()).get(); + a = a.get(); + C c = new C(); + c = c.setAndGet(new C()); + } } ``` @@ -3696,7 +3801,8 @@ class A extends SelfBounded{} ``` 这会强制要求将正在定义的类当作参数传递给基类。 -自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在B类的定义中所看到的,还可以从使用了另一个 **SelfBounded** 参数的**SelfBounded** 中导出,尽管在 **A** 类看到的用法看起来是主要的用法。对定义 **E** 的尝试说明不能使用不是 **SelfBounded** 的类型参数。 + +自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在 B 类的定义中所看到的,还可以从使用了另一个 **SelfBounded** 参数的 **SelfBounded** 中导出,尽管在 **A** 类看到的用法看起来是主要的用法。对定义 **E** 的尝试说明不能使用不是 **SelfBounded** 的类型参数。 遗憾的是, **F** 可以编译,不会有任何警告,因此自限定惯用法不是可强制执行的。如果它确实很重要,可以要求一个外部工具来确保不会使用原生类型来替代参数化类型。 注意,可以移除自限定这个限制,这样所有的类仍旧是可以编译的,但是 **E** 也会因此而变得可编译: @@ -3704,19 +3810,22 @@ class A extends SelfBounded{} // generics/NotSelfBounded.java public class NotSelfBounded { - T element; - NotSelfBounded set(T arg) { - element = arg; - return this; - } - T get() { return element; } -} + T element; + NotSelfBounded set(T arg) { + element = arg; + return this; + } + T get() { return element; } +} class A2 extends NotSelfBounded {} class B2 extends NotSelfBounded {} class C2 extends NotSelfBounded { - C2 setAndGet(C2 arg) { set(arg); return get(); } + C2 setAndGet(C2 arg) { + set(arg); + return get(); + } } class D2 {} @@ -3734,12 +3843,13 @@ class E2 extends NotSelfBounded {} // Visit http://OnJava8.com for more book information. public class SelfBoundingMethods { - static > T f(T arg) { - return arg.set(arg).get(); - } - public static void main(String[] args) { - A a = f(new A()); - } + static > T f(T arg) { + return arg.set(arg).get(); + } + + public static void main(String[] args) { + A a = f(new A()); + } } ``` @@ -3749,7 +3859,7 @@ public class SelfBoundingMethods { 自限定类型的价值在于它们可以产生*协变参数类型*——方法参数类型会随子类而变化。 -尽管自限定类型还可以产生于子类类型相同的返回类型,但是这并不十分重要,因为*协变返回类型*是在 Java SE5 中引入的: +尽管自限定类型还可以产生与子类类型相同的返回类型,但是这并不十分重要,因为*协变返回类型*是在 Java 5 引入: ```java // generics/CovariantReturnTypes.java @@ -3758,43 +3868,44 @@ class Base {} class Derived extends Base {} interface OrdinaryGetter { - Base get(); + Base get(); } interface DerivedGetter extends OrdinaryGetter { - // Overridden method return type can vary: - @Override - Derived get(); + // Overridden method return type can vary: + @Override + Derived get(); } public class CovariantReturnTypes { - void test(DerivedGetter d) { - Derived d2 = d.get(); - } + void test(DerivedGetter d) { + Derived d2 = d.get(); + } } ``` **DerivedGetter** 中的 `get()` 方法覆盖了 **OrdinaryGetter** 中的 `get()` ,并返回了一个从 `OrdinaryGetter.get()` 的返回类型中导出的类型。尽管这是完全合乎逻辑的事情(导出类方法应该能够返回比它覆盖的基类方法更具体的类型)但是这在早先的 Java 版本中是不合法的。 + 自限定泛型事实上将产生确切的导出类型作为其返回值,就像在 `get()` 中所看到的一样: ```java // generics/GenericsAndReturnTypes.java interface GenericGetter> { - T get(); + T get(); } interface Getter extends GenericGetter {} public class GenericsAndReturnTypes { - void test(Getter g) { - Getter result = g.get(); - GenericGetter gg = g.get(); // Also the base type - } + void test(Getter g) { + Getter result = g.get(); + GenericGetter gg = g.get(); // Also the base type + } } ``` -注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java SE5 。 +注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java 5。 然而,在非泛型代码中,参数类型不能随子类型发生变化: @@ -3802,26 +3913,26 @@ public class GenericsAndReturnTypes { // generics/OrdinaryArguments.java class OrdinarySetter { - void set(Base base) { - System.out.println("OrdinarySetter.set(Base)"); - } + void set(Base base) { + System.out.println("OrdinarySetter.set(Base)"); + } } class DerivedSetter extends OrdinarySetter { - void set(Derived derived) { - System.out.println("DerivedSetter.set(Derived)"); - } + void set(Derived derived) { + System.out.println("DerivedSetter.set(Derived)"); + } } public class OrdinaryArguments { - public static void main(String[] args) { - Base base = new Base(); - Derived derived = new Derived(); - DerivedSetter ds = new DerivedSetter(); - ds.set(derived); - // Compiles--overloaded, not overridden!: - ds.set(base); - } + public static void main(String[] args) { + Base base = new Base(); + Derived derived = new Derived(); + DerivedSetter ds = new DerivedSetter(); + ds.set(derived); + // Compiles--overloaded, not overridden!: + ds.set(base); + } } /* Output: DerivedSetter.set(Derived) @@ -3835,60 +3946,59 @@ OrdinarySetter.set(Base) ```java // generics/SelfBoundingAndCovariantArguments.java -interface -SelfBoundSetter> { - void set(T arg); +interface SelfBoundSetter> { + void set(T arg); } interface Setter extends SelfBoundSetter {} public class SelfBoundingAndCovariantArguments { - void - testA(Setter s1, Setter s2, SelfBoundSetter sbs) { - s1.set(s2); - //- s1.set(sbs); - // error: method set in interface SelfBoundSetter - // cannot be applied to given types; - // s1.set(sbs); - // ^ - // required: Setter - // found: SelfBoundSetter - // reason: argument mismatch; - // SelfBoundSetter cannot be converted to Setter - // where T is a type-variable: - // T extends SelfBoundSetter declared in - // interface SelfBoundSetter - // 1 error - } + void + testA(Setter s1, Setter s2, SelfBoundSetter sbs) { + s1.set(s2); + //- s1.set(sbs); + // error: method set in interface SelfBoundSetter + // cannot be applied to given types; + // s1.set(sbs); + // ^ + // required: Setter + // found: SelfBoundSetter + // reason: argument mismatch; + // SelfBoundSetter cannot be converted to Setter + // where T is a type-variable: + // T extends SelfBoundSetter declared in + // interface SelfBoundSetter + // 1 error + } } ``` 编译器不能识别将基类型当作参数传递给 `set()` 的尝试,因为没有任何方法具有这样的签名。实际上,这个参数已经被覆盖。 如果不使用自限定类型,普通的继承机制就会介入,而你将能够重载,就像在非泛型的情况下一样: -``` +```java // generics/PlainGenericInheritance.java class GenericSetter { // Not self-bounded - void set(T arg) { - System.out.println("GenericSetter.set(Base)"); - } + void set(T arg) { + System.out.println("GenericSetter.set(Base)"); + } } class DerivedGS extends GenericSetter { - void set(Derived derived) { - System.out.println("DerivedGS.set(Derived)"); - } + void set(Derived derived) { + System.out.println("DerivedGS.set(Derived)"); + } } public class PlainGenericInheritance { - public static void main(String[] args) { - Base base = new Base(); - Derived derived = new Derived(); - DerivedGS dgs = new DerivedGS(); - dgs.set(derived); - dgs.set(base); // Overloaded, not overridden! - } + public static void main(String[] args) { + Base base = new Base(); + Derived derived = new Derived(); + DerivedGS dgs = new DerivedGS(); + dgs.set(derived); + dgs.set(base); // Overloaded, not overridden! + } } /* Output: DerivedGS.set(Derived) @@ -3896,16 +4006,16 @@ GenericSetter.set(Base) */ ``` -这段代码在模仿 **OrdinaryArgument.java** ,在那个示例中,**DerivedSetter** 继承自包含一个 `set(Base)` 的**OrdinarySetter** 。而这里,**DerivedGS** 继承自泛型创建的也包含有一个 `set(Base)`的 `GenericSetter`。就像 **OrdinaryArgument.java** 一样,你可以从输出中看到, **DerivedGS** 包含两个 `set()` 的重载版本。如果不使用自限定,将重载参数类型。如果使用了自限定,只能获得某个方法的一个版本,它将接受确切的参数类型。 +这段代码在模仿 **OrdinaryArguments.java**;在那个示例中,**DerivedSetter** 继承自包含一个 `set(Base)` 的**OrdinarySetter** 。而这里,**DerivedGS** 继承自泛型创建的也包含有一个 `set(Base)`的 `GenericSetter`。就像 **OrdinaryArguments.java** 一样,你可以从输出中看到, **DerivedGS** 包含两个 `set()` 的重载版本。如果不使用自限定,将重载参数类型。如果使用了自限定,只能获得方法的一个版本,它将接受确切的参数类型。 + + ## 动态类型安全 - +因为可以向 Java 5 之前的代码传递泛型集合,所以旧式代码仍旧有可能会破坏你的集合。Java 5 的 **java.util.Collections** 中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是:静态方法 `checkedCollection()` 、`checkedList()`、 `checkedMap()` 、 `checkedSet()` 、`checkedSortedMap()`和 `checkedSortedSet()`。这些方法每一个都会将你希望动态检查的集合当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。 -因为可以向 Java SE5 之前的代码传递泛型容器,所以旧式代码仍旧有可能会破坏你的容器,Java SE5 的 **java.util.Collections** 中有一组便利工具,可以解决在这种强况下的类型检查问题,它们是:静态方法`checkedCollection()` 、`checkedList()`、 `checkedMap()` 、 `checkedSet()` 、`checkedSortedMap()`和 `checkedSortedSet()`。这些方法每一个都会将你希望动态检查的容器当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。 - -受检查的容器在你试图插入类型不正确的对象时抛出 **ClassCastException** ,这与泛型之前的(原生)容器形成了对比,对于后者来说,当你将对象从容器中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的容器,就可以发现谁在试图插入不良对象。 -让我们用受检查的容器来看看“将猫插入到狗列表中”这个问题。这里,`oldStyleMethod()` 表示遗留代码,因为它接受的是原生的 **List** ,而 **@SuppressWarnings(“unchecked”)** 注解对于压制所产生的警告是必需的: +受检查的集合在你试图插入类型不正确的对象时抛出 **ClassCastException** ,这与泛型之前的(原生)集合形成了对比,对于后者来说,当你将对象从集合中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的集合,就可以发现谁在试图插入不良对象。 +让我们用受检查的集合来看看“将猫插入到狗列表中”这个问题。这里,`oldStyleMethod()` 表示遗留代码,因为它接受的是原生的 **List** ,而 **@SuppressWarnings(“unchecked”)** 注解对于压制所产生的警告是必需的: ```java // generics/CheckedList.java @@ -3914,26 +4024,27 @@ import typeinfo.pets.*; import java.util.*; public class CheckedList { - @SuppressWarnings("unchecked") - static void oldStyleMethod(List probablyDogs) { - probablyDogs.add(new Cat()); - } - public static void main(String[] args) { - List dogs1 = new ArrayList<>(); - oldStyleMethod(dogs1); // Quietly accepts a Cat - List dogs2 = Collections.checkedList( - new ArrayList<>(), Dog.class); - try { - oldStyleMethod(dogs2); // Throws an exception - } catch(Exception e) { - System.out.println("Expected: " + e); + @SuppressWarnings("unchecked") + static void oldStyleMethod(List probablyDogs) { + probablyDogs.add(new Cat()); + } + + public static void main(String[] args) { + List dogs1 = new ArrayList<>(); + oldStyleMethod(dogs1); // Quietly accepts a Cat + List dogs2 = Collections.checkedList( + new ArrayList<>(), Dog.class); + try { + oldStyleMethod(dogs2); // Throws an exception + } catch(Exception e) { + System.out.println("Expected: " + e); + } + // Derived types work fine: + List pets = Collections.checkedList( + new ArrayList<>(), Pet.class); + pets.add(new Dog()); + pets.add(new Cat()); } - // Derived types work fine: - List pets = Collections.checkedList( - new ArrayList<>(), Pet.class); - pets.add(new Dog()); - pets.add(new Cat()); - } } /* Output: Expected: java.lang.ClassCastException: Attempt to @@ -3944,12 +4055,12 @@ with element type class typeinfo.pets.Dog 运行这个程序时,你会发现插入一个 **Cat** 对于 **dogs1** 来说没有任何问题,而 **dogs2** 立即会在这个错误类型的插入操作上抛出一个异常。还可以看到,将导出类型的对象放置到将要检查基类型的受检查容器中是没有问题的。 + + ## 泛型异常 - - -由于擦除的原因,将泛型应用于异常是非常受限的。**catch** 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 **Throwable**(这将进一步阻止你去定义不能捕获的泛型异常)。 -但是,类型参数可能会在一个方法的 **throws** 子句中用到。这使得你可以编写随检查型异常的类型而发生变化的泛型代码: +由于擦除的原因,**catch** 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 **Throwable**(这将进一步阻止你去定义不能捕获的泛型异常)。 +但是,类型参数可能会在一个方法的 **throws** 子句中用到。这使得你可以编写随检查型异常类型变化的泛型代码: ```java // generics/ThrowGenericException.java @@ -3957,76 +4068,76 @@ with element type class typeinfo.pets.Dog import java.util.*; interface Processor { - void process(List resultCollector) throws E; + void process(List resultCollector) throws E; } class ProcessRunner extends ArrayList> { - List processAll() throws E { - List resultCollector = new ArrayList<>(); - for(Processor processor : this) - processor.process(resultCollector); - return resultCollector; - } + List processAll() throws E { + List resultCollector = new ArrayList<>(); + for(Processor processor : this) + processor.process(resultCollector); + return resultCollector; + } } class Failure1 extends Exception {} class Processor1 implements Processor { - static int count = 3; - @Override - public void process(List resultCollector) - throws Failure1 { - if(count-- > 1) - resultCollector.add("Hep!"); - else - resultCollector.add("Ho!"); - if(count < 0) - throw new Failure1(); - } + static int count = 3; + @Override + public void process(List resultCollector) + throws Failure1 { + if(count-- > 1) + resultCollector.add("Hep!"); + else + resultCollector.add("Ho!"); + if(count < 0) + throw new Failure1(); + } } class Failure2 extends Exception {} class Processor2 implements Processor { - static int count = 2; - @Override - public void process(List resultCollector) - throws Failure2 { - if(count-- == 0) - resultCollector.add(47); - else { - resultCollector.add(11); + static int count = 2; + @Override + public void process(List resultCollector) + throws Failure2 { + if(count-- == 0) + resultCollector.add(47); + else { + resultCollector.add(11); + } + if(count < 0) + throw new Failure2(); } - if(count < 0) - throw new Failure2(); - } } public class ThrowGenericException { - public static void main(String[] args) { - ProcessRunner runner = - new ProcessRunner<>(); - for(int i = 0; i < 3; i++) - runner.add(new Processor1()); - try { - System.out.println(runner.processAll()); - } catch(Failure1 e) { - System.out.println(e); - } + public static void main(String[] args) { + ProcessRunner runner = + new ProcessRunner<>(); + for(int i = 0; i < 3; i++) + runner.add(new Processor1()); + try { + System.out.println(runner.processAll()); + } catch(Failure1 e) { + System.out.println(e); + } - ProcessRunner runner2 = - new ProcessRunner<>(); - for(int i = 0; i < 3; i++) - runner2.add(new Processor2()); - try { - System.out.println(runner2.processAll()); - } catch(Failure2 e) { - System.out.println(e); + ProcessRunner runner2 = + new ProcessRunner<>(); + for(int i = 0; i < 3; i++) + runner2.add(new Processor2()); + try { + System.out.println(runner2.processAll()); + } catch(Failure2 e) { + System.out.println(e); + } } - } } /* Output: [Hep!, Hep!, Ho!] @@ -4034,20 +4145,22 @@ Failure2 */ ``` -**Processor** 执行 `process()`,并且可能会抛出具有类型 **E** 的异常。`process()` 的结果存储在 `ListresultCollector` 中(这被称为*收集参数*)。**ProcessRunner** 有一个 `processAll()` 方法,它将执行所持有的每个 **Process** 对象,并返回 **resultCollector** 。 +**Processor** 执行 `process()` 方法,并且可能会抛出具有类型 **E** 的异常。`process()` 的结果存储在 `ListresultCollector` 中(这被称为*收集参数*)。**ProcessRunner** 有一个 `processAll()` 方法,它会在所持有的每个 **Process** 对象执行,并返回 **resultCollector** 。 如果不能参数化所抛出的异常,那么由于检查型异常的缘故,将不能编写出这种泛化的代码。 + + ## 混型 术语*混型*随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。 -混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向切面编程* (AOP) 的味道,而方面经常被建议用来解决混型问题。 +混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向切面编程* (AOP) 的味道,而切面经常被建议用来解决混型问题。 -### C++中的混型 +### C++ 中的混型 -在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易的创建混型,因为 C++ 能够记住其模版参数的类型。 +在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易地创建混型,因为 C++ 能够记住其模版参数的类型。 下面是一个 C++ 示例,它有两个混型类型:一个使得你可以在每个对象中混入拥有一个时间戳这样的属性,而另一个可以混入一个序列号。 -```cpp +```c++ // generics/Mixins.cpp #include @@ -4056,38 +4169,38 @@ Failure2 using namespace std; template class TimeStamped : public T { - long timeStamp; + long timeStamp; public: - TimeStamped() { timeStamp = time(0); } - long getStamp() { return timeStamp; } + TimeStamped() { timeStamp = time(0); } + long getStamp() { return timeStamp; } }; template class SerialNumbered : public T { - long serialNumber; - static long counter; + long serialNumber; + static long counter; public: - SerialNumbered() { serialNumber = counter++; } - long getSerialNumber() { return serialNumber; } + SerialNumbered() { serialNumber = counter++; } + long getSerialNumber() { return serialNumber; } }; // Define and initialize the static storage: template long SerialNumbered::counter = 1; class Basic { - string value; + string value; public: - void set(string val) { value = val; } - string get() { return value; } + void set(string val) { value = val; } + string get() { return value; } }; int main() { - TimeStamped> mixin1, mixin2; - mixin1.set("test string 1"); - mixin2.set("test string 2"); - cout << mixin1.get() << " " << mixin1.getStamp() << - " " << mixin1.getSerialNumber() << endl; - cout << mixin2.get() << " " << mixin2.getStamp() << - " " << mixin2.getSerialNumber() << endl; + TimeStamped> mixin1, mixin2; + mixin1.set("test string 1"); + mixin2.set("test string 2"); + cout << mixin1.get() << " " << mixin1.getStamp() << + " " << mixin1.getSerialNumber() << endl; + cout << mixin2.get() << " " << mixin2.getStamp() << + " " << mixin2.getSerialNumber() << endl; } /* Output: test string 1 1452987605 1 @@ -4095,17 +4208,17 @@ test string 2 1452987605 2 */ ``` -在 `main()` 中, **mixin1** 和 **mixin2** 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么地轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了: +在 `main()` 中, **mixin1** 和 **mixin2** 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么的轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了: -``` +```c++ TimeStamped> mixin1,mixin2; ``` -遗憾的是,Java泛型不允许这样。擦除会忘记基类类型,因此 +遗憾的是,Java 泛型不允许这样。擦除会忘记基类类型,因此 > 泛型类不能直接继承自一个泛型参数 -这突显了许多我在Java语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您发现自己做不到。 +这突显了许多我在 Java 语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您会发现自己做不到。 ### 与接口混合 @@ -4119,63 +4232,61 @@ import java.util.*; interface TimeStamped { long getStamp(); } class TimeStampedImp implements TimeStamped { - private final long timeStamp; - TimeStampedImp() { - timeStamp = new Date().getTime(); - } - @Override - public long getStamp() { return timeStamp; } + private final long timeStamp; + TimeStampedImp() { + timeStamp = new Date().getTime(); + } + @Override + public long getStamp() { return timeStamp; } } interface SerialNumbered { long getSerialNumber(); } class SerialNumberedImp implements SerialNumbered { - private static long counter = 1; - private final long serialNumber = counter++; - @Override - public long getSerialNumber() { return serialNumber; } + private static long counter = 1; + private final long serialNumber = counter++; + @Override + public long getSerialNumber() { return serialNumber; } } interface Basic { - void set(String val); - String get(); + void set(String val); + String get(); } class BasicImp implements Basic { - private String value; - @Override - public void set(String val) { value = val; } - @Override - public String get() { return value; } + private String value; + @Override + public void set(String val) { value = val; } + @Override + public String get() { return value; } } class Mixin extends BasicImp implements TimeStamped, SerialNumbered { - private TimeStamped timeStamp = new TimeStampedImp(); - private SerialNumbered serialNumber = - new SerialNumberedImp(); - @Override - public long getStamp() { - return timeStamp.getStamp(); - } - @Override - public long getSerialNumber() { - return serialNumber.getSerialNumber(); - } + private TimeStamped timeStamp = new TimeStampedImp(); + private SerialNumbered serialNumber = + new SerialNumberedImp(); + @Override + public long getStamp() { + return timeStamp.getStamp(); + } + @Override + public long getSerialNumber() { + return serialNumber.getSerialNumber(); + } } public class Mixins { - public static void main(String[] args) { - Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); - mixin1.set("test string 1"); - mixin2.set("test string 2"); - System.out.println(mixin1.get() + " " + - mixin1.getStamp() + " " + - mixin1.getSerialNumber()); - System.out.println(mixin2.get() + " " + - mixin2.getStamp() + " " + - mixin2.getSerialNumber()); - } + public static void main(String[] args) { + Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); + mixin1.set("test string 1"); + mixin2.set("test string 2"); + System.out.println(mixin1.get() + " " + + mixin1.getStamp() + " " + mixin1.getSerialNumber()); + System.out.println(mixin2.get() + " " + + mixin2.getStamp() + " " + mixin2.getSerialNumber()); + } } /* Output: test string 1 1494331663026 1 @@ -4183,12 +4294,12 @@ test string 2 1494331663027 2 */ ``` -**Mixin** 类基本上是在使用*代理*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。 +**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。 ### 使用装饰器模式 当你观察混型的使用方式时,就会发现混型概念好像与*装饰器*设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。 -装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的一—无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。 +装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的——无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。 装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将基于参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。 前面的示例可以被改写为使用装饰器: @@ -4200,46 +4311,46 @@ package generics.decorator; import java.util.*; class Basic { - private String value; - public void set(String val) { value = val; } - public String get() { return value; } + private String value; + public void set(String val) { value = val; } + public String get() { return value; } } class Decorator extends Basic { - protected Basic basic; - Decorator(Basic basic) { this.basic = basic; } - @Override - public void set(String val) { basic.set(val); } - @Override - public String get() { return basic.get(); } + protected Basic basic; + Decorator(Basic basic) { this.basic = basic; } + @Override + public void set(String val) { basic.set(val); } + @Override + public String get() { return basic.get(); } } class TimeStamped extends Decorator { - private final long timeStamp; - TimeStamped(Basic basic) { - super(basic); - timeStamp = new Date().getTime(); - } - public long getStamp() { return timeStamp; } + private final long timeStamp; + TimeStamped(Basic basic) { + super(basic); + timeStamp = new Date().getTime(); + } + public long getStamp() { return timeStamp; } } class SerialNumbered extends Decorator { - private static long counter = 1; - private final long serialNumber = counter++; - SerialNumbered(Basic basic) { super(basic); } - public long getSerialNumber() { return serialNumber; } + private static long counter = 1; + private final long serialNumber = counter++; + SerialNumbered(Basic basic) { super(basic); } + public long getSerialNumber() { return serialNumber; } } public class Decoration { - public static void main(String[] args) { - TimeStamped t = new TimeStamped(new Basic()); - TimeStamped t2 = new TimeStamped( - new SerialNumbered(new Basic())); - //- t2.getSerialNumber(); // Not available - SerialNumbered s = new SerialNumbered(new Basic()); - SerialNumbered s2 = new SerialNumbered( - new TimeStamped(new Basic())); - //- s2.getStamp(); // Not available + public static void main(String[] args) { + TimeStamped t = new TimeStamped(new Basic()); + TimeStamped t2 = new TimeStamped( + new SerialNumbered(new Basic())); + //- t2.getSerialNumber(); // Not available + SerialNumbered s = new SerialNumbered(new Basic()); + SerialNumbered s2 = new SerialNumbered( + new TimeStamped(new Basic())); + //- s2.getStamp(); // Not available } } ``` @@ -4248,7 +4359,7 @@ public class Decoration { ### 与动态代理混合 -可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看类型信息(Type Information) 章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。 +可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看 [类型信息](book/19-Type-Information.md) 一章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。 由于动态代理的限制,每个被混入的类都必须是某个接口的实现: ```java @@ -4260,55 +4371,53 @@ import onjava.*; import static onjava.Tuple.*; class MixinProxy implements InvocationHandler { - Map delegatesByMethod; - @SuppressWarnings("unchecked") - MixinProxy(Tuple2>... pairs) { - delegatesByMethod = new HashMap<>(); - for(Tuple2> pair : pairs) { - for(Method method : pair.a2.getMethods()) { + Map delegatesByMethod; + @SuppressWarnings("unchecked") + MixinProxy(Tuple2>... pairs) { + delegatesByMethod = new HashMap<>(); + for(Tuple2> pair : pairs) { + for(Method method : pair.a2.getMethods()) { + String methodName = method.getName(); + // The first interface in the map + // implements the method. + if(!delegatesByMethod.containsKey(methodName)) + delegatesByMethod.put(methodName, pair.a1); + } + } + } + @Override + public Object invoke(Object proxy, Method method, + Object[] args) throws Throwable { String methodName = method.getName(); - // The first interface in the map - // implements the method. - if(!delegatesByMethod.containsKey(methodName)) - delegatesByMethod.put(methodName, pair.a1); - } + Object delegate = delegatesByMethod.get(methodName); + return method.invoke(delegate, args); } - } - @Override - public Object invoke(Object proxy, Method method, - Object[] args) throws Throwable { - String methodName = method.getName(); - Object delegate = delegatesByMethod.get(methodName); - return method.invoke(delegate, args); - } - @SuppressWarnings("unchecked") - public static Object newInstance(Tuple2... pairs) { - Class[] interfaces = new Class[pairs.length]; - for(int i = 0; i < pairs.length; i++) { - interfaces[i] = (Class)pairs[i].a2; + + @SuppressWarnings("unchecked") + public static Object newInstance(Tuple2... pairs) { + Class[] interfaces = new Class[pairs.length]; + for(int i = 0; i < pairs.length; i++) { + interfaces[i] = (Class)pairs[i].a2; + } + ClassLoader cl = pairs[0].a1.getClass().getClassLoader(); + return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs)); } - ClassLoader cl = - pairs[0].a1.getClass().getClassLoader(); - return Proxy.newProxyInstance( - cl, interfaces, new MixinProxy(pairs)); - } } public class DynamicProxyMixin { - public static void main(String[] args) { - Object mixin = MixinProxy.newInstance( - tuple(new BasicImp(), Basic.class), - tuple(new TimeStampedImp(), TimeStamped.class), - tuple(new SerialNumberedImp(), - SerialNumbered.class)); - Basic b = (Basic)mixin; - TimeStamped t = (TimeStamped)mixin; - SerialNumbered s = (SerialNumbered)mixin; - b.set("Hello"); - System.out.println(b.get()); - System.out.println(t.getStamp()); - System.out.println(s.getSerialNumber()); - } + public static void main(String[] args) { + Object mixin = MixinProxy.newInstance( + tuple(new BasicImp(), Basic.class), + tuple(new TimeStampedImp(), TimeStamped.class), + tuple(new SerialNumberedImp(), SerialNumbered.class)); + Basic b = (Basic)mixin; + TimeStamped t = (TimeStamped)mixin; + SerialNumbered s = (SerialNumbered)mixin; + b.set("Hello"); + System.out.println(b.get()); + System.out.println(t.getStamp()); + System.out.println(s.getSerialNumber()); + } } /* Output: Hello @@ -4317,7 +4426,7 @@ Hello */ ``` -因为只有动态类型而不是非静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。 +因为只有动态类型而不是静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。 为了让 Java 支持混型,人们已经做了大量的工作朝着这个目标努力,包括创建了至少一种附加语言( Jam 语言),它是专门用来支持混型的。 @@ -4325,13 +4434,18 @@ Hello ## 潜在类型机制 在本章的开头介绍过这样的思想,即要编写能够尽可能广泛地应用的代码。为了实现这一点,我们需要各种途径来放松对我们的代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,我们就可以编写出无需修改就可以应用于更多情况的代码,即更加“泛化”的代码。 -Java泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动包装机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。 -还是正如你所见到的,当要在泛型类型上执行操作(即调用 **Object** 方法之前的操作)时,就会产生问题,因为擦除要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。 -某些编程语言提供的一种解决方案称为潜在类型机制或结构化类型机制,而更古怪的术语称为鸭子类型机制,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。 -泛型代码典型地将在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 `speak()` 和 `sit()` 即可。”由于不要求具体类型,因此代码就可以更加泛化。 -潜在类型机制是一种代码组织和复用机制。有了它编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。 -两种支持潜在类型机制的语言实例是 Python (可以从www.Python.org免费下载)和 C++。Python是动态类型语言(事实上所有的类型检查都发生在运行时),而C++是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。 +Java 泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动装箱机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。 + +还是正如你所见到的,当要在泛型类型上执行操作(即调用 **Object** 方法之外的方法)时,就会产生问题。擦除强制要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。 + +某些编程语言提供的一种解决方案称为*潜在类型机制*或*结构化类型机制*,而更古怪的术语称为*鸭子类型机制*,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。 + +泛型代码典型地只能在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 `speak()` 和 `sit()` 即可。”由于不要求具体类型,因此代码就可以更加泛化。 + +潜在类型机制是一种代码组织和复用机制。有了它,编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。 + +支持潜在类型机制的语言包括 Python(可以从 www.Python.org 免费下载)、C++、Ruby、SmallTalk 和 Go。Python 是动态类型语言(几乎所有的类型检查都发生在运行时),而 C++ 和 Go 是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。 ### pyhton 中的潜在类型 @@ -4373,15 +4487,15 @@ Clank! """ ``` -Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“**#**” 表示注释到行尾,就像Java中的 “ **//** ”。类的方法需要显式地指定 **this** 引用的等价物作为第一个参数,按惯例成为 **self** 。构造器调用不要求任何类型的“ **new** ”关键字,并且 Python 允许正则(非成员)函数,就像 `perform()` 所表明的那样。注意,在 `perform(anything)`中,没有任何针对 **anything** 的类型,**anything** 只是一个标识符,它必须能够执行 `perform()` 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。`perform()` 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 `speak()` 和 `sit()`方法。如果传递给 `perform()` 的对象不支持这些操作,那么将会得到运行时异常。 +Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“**#**” 表示注释到行尾,就像Java中的 “ **//** ”。类的方法需要显式地指定 **this** 引用的等价物作为第一个参数,按惯例成为 **self** 。构造器调用不要求任何类型的“ **new** ”关键字,并且 Python 允许普通(非成员)函数,就像 `perform()` 所表明的那样。注意,在 `perform(anything)` 中,没有任何针对 **anything** 的类型,**anything** 只是一个标识符,它必须能够执行 `perform()` 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。`perform()` 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 `speak()` 和 `sit()` 方法。如果传递给 `perform()` 的对象不支持这些操作,那么将会得到运行时异常。 -输出规定使用三重引号创建带有嵌入式换行符的字符串。 +输出规定使用三重引号创建带有内嵌换行符的字符串。 ### C++ 中的潜在类型 -我们可以用C++产生相同的效果: +我们可以用 C++ 产生相同的效果: -```cpp +```c++ // generics/DogsAndRobots.cpp #include @@ -4389,28 +4503,28 @@ using namespace std; class Dog { public: - void speak() { cout << "Arf!" << endl; } - void sit() { cout << "Sitting" << endl; } - void reproduce() {} + void speak() { cout << "Arf!" << endl; } + void sit() { cout << "Sitting" << endl; } + void reproduce() {} }; class Robot { public: - void speak() { cout << "Click!" << endl; } - void sit() { cout << "Clank!" << endl; } - void oilChange() {} + void speak() { cout << "Click!" << endl; } + void sit() { cout << "Clank!" << endl; } + void oilChange() {} }; template void perform(T anything) { - anything.speak(); - anything.sit(); + anything.speak(); + anything.sit(); } int main() { - Dog d; - Robot r; - perform(d); - perform(r); + Dog d; + Robot r; + perform(d); + perform(r); } /* Output: Arf! @@ -4421,13 +4535,13 @@ Clank! ``` 在 Python 和 C++ 中,**Dog** 和 **Robot** 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,`perform()` 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。 -C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,而主要原因是因为 C++ 的模版名声欠佳)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。 +C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。 ### Go 中的潜在类型 -这是用Go语言编写的相同程序: +这里用 Go 语言编写相同的程序: -```java +```go // generics/dogsandrobots.go package main @@ -4460,26 +4574,26 @@ Clank! */ ``` -Go 没有 **class** 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是将其定义为 **struct** ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 **func** 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 **this** 来提醒您,就像在 C ++ 或 Java 中的 **this** 一样。 然后,在Go中像这样定义其余的函数。 +Go 没有 **class** 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是定义为 **struct** ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 **func** 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 **this** 来提醒您,就像在 C ++ 或 Java 中的 **this** 一样。 然后,在Go中像这样定义其余的函数。 -Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要的原因。 但是,Go 的组成很简单。 +Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要原因。 但是,Go 的组成很简单。 `perform()` 函数使用潜在类型:参数的确切类型并不重要,只要它包含了 `speak()` 和 `sit()` 方法即可。 该接口在此处匿名定义,内联,如 `perform()` 的参数列表所示。 -`main()` 证明 `perform()` 确实对其参数的确切类型无关紧要,只要可以在该参数上调用 `talk()` 和 `sit()` 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。 +`main()` 证明 `perform()` 确实对其参数的确切类型不在乎,只要可以在该参数上调用 `talk()` 和 `sit()` 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。 语法 **Dog {}** 和 **Robot {}** 创建匿名的 **Dog** 和 **Robot** 结构。 ### java中的直接潜在类型 -因为泛型是在这场竞赛的后期才添加到 Java 中的,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用删除来实现Java泛型的实现有时称为第二类泛型类型)例如,在 Java8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它: +因为泛型是在这场竞赛的后期才添加到 Java 中,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用擦除来实现 Java 泛型的实现有时称为第二类泛型类型)例如,在 Java 8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它: ```java // generics/Performs.java public interface Performs { - void speak(); - void sit(); + void speak(); + void sit(); } ``` @@ -4489,32 +4603,32 @@ public interface Performs { import typeinfo.pets.*; class PerformingDog extends Dog implements Performs { - @Override - public void speak() { System.out.println("Woof!"); } - @Override - public void sit() { System.out.println("Sitting"); } - public void reproduce() {} + @Override + public void speak() { System.out.println("Woof!"); } + @Override + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} } class Robot implements Performs { - public void speak() { System.out.println("Click!"); } - public void sit() { System.out.println("Clank!"); } - public void oilChange() {} + public void speak() { System.out.println("Click!"); } + public void sit() { System.out.println("Clank!"); } + public void oilChange() {} } class Communicate { - public static - void perform(T performer) { - performer.speak(); - performer.sit(); - } + public static + void perform(T performer) { + performer.speak(); + performer.sit(); + } } public class DogsAndRobots { - public static void main(String[] args) { - Communicate.perform(new PerformingDog()); - Communicate.perform(new Robot()); - } + public static void main(String[] args) { + Communicate.perform(new PerformingDog()); + Communicate.perform(new Robot()); + } } /* Output: Woof! @@ -4531,17 +4645,17 @@ Clank! // Removing the generic; code still works class CommunicateSimply { - static void perform(Performs performer) { - performer.speak(); - performer.sit(); - } + static void perform(Performs performer) { + performer.speak(); + performer.sit(); + } } public class SimpleDogsAndRobots { - public static void main(String[] args) { - CommunicateSimply.perform(new PerformingDog()); - CommunicateSimply.perform(new Robot()); - } + public static void main(String[] args) { + CommunicateSimply.perform(new PerformingDog()); + CommunicateSimply.perform(new Robot()); + } } /* Output: Woof! @@ -4557,7 +4671,7 @@ Clank! ## 对缺乏潜在类型机制的补偿 -尽管 Java 不支持潜在类型机制,但是这并不意味着有界泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。 +尽管 Java 不直接支持潜在类型机制,但是这并不意味着泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。 ### 反射 @@ -4570,53 +4684,53 @@ import java.lang.reflect.*; // Does not implement Performs: class Mime { - public void walkAgainstTheWind() {} - public void sit() { - System.out.println("Pretending to sit"); - } - public void pushInvisibleWalls() {} - @Override - public String toString() { return "Mime"; } + public void walkAgainstTheWind() {} + public void sit() { + System.out.println("Pretending to sit"); + } + public void pushInvisibleWalls() {} + @Override + public String toString() { return "Mime"; } } // Does not implement Performs: class SmartDog { - public void speak() { System.out.println("Woof!"); } - public void sit() { System.out.println("Sitting"); } - public void reproduce() {} + public void speak() { System.out.println("Woof!"); } + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} } class CommunicateReflectively { - public static void perform(Object speaker) { - Class spkr = speaker.getClass(); - try { - try { - Method speak = spkr.getMethod("speak"); - speak.invoke(speaker); - } catch(NoSuchMethodException e) { - System.out.println(speaker + " cannot speak"); - } - try { - Method sit = spkr.getMethod("sit"); - sit.invoke(speaker); - } catch(NoSuchMethodException e) { - System.out.println(speaker + " cannot sit"); - } - } catch(SecurityException | + public static void perform(Object speaker) { + Class spkr = speaker.getClass(); + try { + try { + Method speak = spkr.getMethod("speak"); + speak.invoke(speaker); + } catch(NoSuchMethodException e) { + System.out.println(speaker + " cannot speak"); + } + try { + Method sit = spkr.getMethod("sit"); + sit.invoke(speaker); + } catch(NoSuchMethodException e) { + System.out.println(speaker + " cannot sit"); + } + } catch(SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(speaker.toString(), e); + throw new RuntimeException(speaker.toString(), e); + } } - } } public class LatentReflection { - public static void main(String[] args) { - CommunicateReflectively.perform(new SmartDog()); - CommunicateReflectively.perform(new Robot()); - CommunicateReflectively.perform(new Mime()); - } + public static void main(String[] args) { + CommunicateReflectively.perform(new SmartDog()); + CommunicateReflectively.perform(new Robot()); + CommunicateReflectively.perform(new Mime()); + } } /* Output: Woof! @@ -4632,9 +4746,9 @@ Pretending to sit ### 将一个方法应用于序列 -反射提供了一些有趣的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗? +反射提供了一些有用的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗? -让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这是接口看起来并不适合的情况,因为你想要将任何方法应用于一个对象集合,而接口对于描述“任何方法”存在过多的限制。如何用Java来实现这个需求呢? +让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这种情况下使用接口不适合,因为你想要将任何方法应用于一个对象集合,而接口不可能描述任何方法。如何用 Java 来实现这个需求呢? 最初,我们可以用反射来解决这个问题,由于有了 Java 的可变参数,这种方式被证明是相当优雅的: @@ -4645,18 +4759,18 @@ import java.lang.reflect.*; import java.util.*; public class Apply { - public static > - void apply(S seq, Method f, Object... args) { - try { - for(T t: seq) - f.invoke(t, args); - } catch(IllegalAccessException | + public static > + void apply(S seq, Method f, Object... args) { + try { + for(T t: seq) + f.invoke(t, args); + } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - // Failures are programmer errors - throw new RuntimeException(e); + // Failures are programmer errors + throw new RuntimeException(e); + } } - } } ``` @@ -4670,18 +4784,18 @@ public class Apply { // generics/Shape.java public class Shape { - private static long counter = 0; - private final long id = counter++; - @Override - public String toString() { - return getClass().getSimpleName() + " " + id; - } - public void rotate() { - System.out.println(this + " rotate"); - } - public void resize(int newSize) { - System.out.println(this + " resize " + newSize); - } + private static long counter = 0; + private final long id = counter++; + @Override + public String toString() { + return getClass().getSimpleName() + " " + id; + } + public void rotate() { + System.out.println(this + " rotate"); + } + public void resize(int newSize) { + System.out.println(this + " resize " + newSize); + } } ``` @@ -4703,35 +4817,30 @@ import java.util.function.*; import onjava.*; public class ApplyTest { - public static - void main(String[] args) throws Exception { - List shapes = - Suppliers.create(ArrayList::new, Shape::new, 3); - Apply.apply(shapes, - Shape.class.getMethod("rotate")); - Apply.apply(shapes, - Shape.class.getMethod("resize", int.class), 7); + public static + void main(String[] args) throws Exception { + List shapes = + Suppliers.create(ArrayList::new, Shape::new, 3); + Apply.apply(shapes, Shape.class.getMethod("rotate")); + Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 7); - List squares = - Suppliers.create(ArrayList::new, Square::new, 3); - Apply.apply(squares, - Shape.class.getMethod("rotate")); - Apply.apply(squares, - Shape.class.getMethod("resize", int.class), 7); + List squares = + Suppliers.create(ArrayList::new, Square::new, 3); + Apply.apply(squares, Shape.class.getMethod("rotate")); + Apply.apply(squares, Shape.class.getMethod("resize", int.class), 7); - Apply.apply(new FilledList<>(Shape::new, 3), - Shape.class.getMethod("rotate")); - Apply.apply(new FilledList<>(Square::new, 3), - Shape.class.getMethod("rotate")); + Apply.apply(new FilledList<>(Shape::new, 3), + Shape.class.getMethod("rotate")); + Apply.apply(new FilledList<>(Square::new, 3), + Shape.class.getMethod("rotate")); - SimpleQueue shapeQ = Suppliers.fill( - new SimpleQueue<>(), SimpleQueue::add, - Shape::new, 3); - Suppliers.fill(shapeQ, SimpleQueue::add, - Square::new, 3); - Apply.apply(shapeQ, - Shape.class.getMethod("rotate")); - } + SimpleQueue shapeQ = Suppliers.fill( + new SimpleQueue<>(), SimpleQueue::add, + Shape::new, 3); + Suppliers.fill(shapeQ, SimpleQueue::add, + Square::new, 3); + Apply.apply(shapeQ, Shape.class.getMethod("rotate")); + } } /* Output: Shape 0 rotate @@ -4761,7 +4870,7 @@ Square 17 rotate */ ``` -在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 容器类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的一例如,在 `main()` 中使用的下面定义的 **SimpleQueue** 类: +在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 集合类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的——例如,在 `main()` 中使用下面定义的 **SimpleQueue** 类: ```java // generics/SimpleQueue.java @@ -4770,19 +4879,19 @@ Square 17 rotate import java.util.*; public class SimpleQueue implements Iterable { - private LinkedList storage = new LinkedList<>(); - public void add(T t) { storage.offer(t); } - public T get() { return storage.poll(); } - @Override - public Iterator iterator() { - return storage.iterator(); - } + private LinkedList storage = new LinkedList<>(); + public void add(T t) { storage.offer(t); } + public T get() { return storage.poll(); } + @Override + public Iterator iterator() { + return storage.iterator(); + } } ``` -正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在Java的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。 +正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在 Java 的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。 -几乎可以肯定,你会首先使用 Java 8 功能方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: +几乎可以肯定,你会首先使用 Java 8 的函数式方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: ```java // generics/ApplyFunctional.java @@ -4793,26 +4902,26 @@ import java.util.function.*; import onjava.*; public class ApplyFunctional { - public static void main(String[] args) { - Stream.of( - Stream.generate(Shape::new).limit(2), - Stream.generate(Square::new).limit(2)) - .flatMap(c -> c) // flatten into one stream - .peek(Shape::rotate) - .forEach(s -> s.resize(7)); + public static void main(String[] args) { + Stream.of( + Stream.generate(Shape::new).limit(2), + Stream.generate(Square::new).limit(2)) + .flatMap(c -> c) // flatten into one stream + .peek(Shape::rotate) + .forEach(s -> s.resize(7)); - new FilledList<>(Shape::new, 2) - .forEach(Shape::rotate); - new FilledList<>(Square::new, 2) - .forEach(Shape::rotate); + new FilledList<>(Shape::new, 2) + .forEach(Shape::rotate); + new FilledList<>(Square::new, 2) + .forEach(Shape::rotate); - SimpleQueue shapeQ = Suppliers.fill( - new SimpleQueue<>(), SimpleQueue::add, - Shape::new, 2); - Suppliers.fill(shapeQ, SimpleQueue::add, - Square::new, 2); - shapeQ.forEach(Shape::rotate); - } + SimpleQueue shapeQ = Suppliers.fill( + new SimpleQueue<>(), SimpleQueue::add, + Shape::new, 2); + Suppliers.fill(shapeQ, SimpleQueue::add, + Square::new, 2); + shapeQ.forEach(Shape::rotate); + } } /* Output: Shape 0 rotate @@ -4846,11 +4955,11 @@ Square 11 rotate ## Java8 中的辅助潜在类型 -先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,该形式可以满足创建可在不相关类型上工作的单段代码的要求。 由于 Java 最初并不是设计用于执行此操作的,因此,正如现在可能期望的那样,其结果比其他语言要尴尬得多。 +先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,以满足创建一段可工作在不相干类型上的代码。因为 Java 最初并不是如此设计,所以结果可想而知,比其他语言中要尴尬一些。但是,至少现在成为了可能,只是缺乏令人惊艳之处。 -我没有在其他地方遇到过这种技术,因此我将其称为辅助潜在类型。 +我在其他地方从没遇过这种技术,因此我将其称为辅助潜在类型。 -我们将重写 **DogsAndRobots.java** 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 **A** : +我们将重写 **DogsAndRobots.java** 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 **A**: ```java // generics/DogsAndRobotMethodReferences.java @@ -4860,35 +4969,35 @@ import typeinfo.pets.*; import java.util.function.*; class PerformingDogA extends Dog { - public void speak() { System.out.println("Woof!"); } - public void sit() { System.out.println("Sitting"); } - public void reproduce() {} + public void speak() { System.out.println("Woof!"); } + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} } class RobotA { - public void speak() { System.out.println("Click!"); } - public void sit() { System.out.println("Clank!"); } - public void oilChange() {} + public void speak() { System.out.println("Click!"); } + public void sit() { System.out.println("Clank!"); } + public void oilChange() {} } class CommunicateA { - public static

void perform(P performer, - Consumer

action1, Consumer

action2) { - action1.accept(performer); - action2.accept(performer); - } + public static

void perform(P performer, + Consumer

action1, Consumer

action2) { + action1.accept(performer); + action2.accept(performer); + } } public class DogsAndRobotMethodReferences { - public static void main(String[] args) { - CommunicateA.perform(new PerformingDogA(), - PerformingDogA::speak, PerformingDogA::sit); - CommunicateA.perform(new RobotA(), - RobotA::speak, RobotA::sit); - CommunicateA.perform(new Mime(), - Mime::walkAgainstTheWind, - Mime::pushInvisibleWalls); - } + public static void main(String[] args) { + CommunicateA.perform(new PerformingDogA(), + PerformingDogA::speak, PerformingDogA::sit); + CommunicateA.perform(new RobotA(), + RobotA::speak, RobotA::sit); + CommunicateA.perform(new Mime(), + Mime::walkAgainstTheWind, + Mime::pushInvisibleWalls); + } } /* Output: Woof! @@ -4900,17 +5009,17 @@ Clank! **PerformingDogA** 和 **RobotA** 与 **DogsAndRobots.java** 中的相同,不同之处在于它们不继承通用接口 **Performs** ,因此它们没有通用性。 -`CommunicateA.perform()` 在没有约束的 **P** 上生成。 只要可以使用 `Consumer

`,它在这里就可以是任何东西,这些 `Consumer

` 代表不带参数的 **P** 方法的未绑定方法引用。 当您调用消费者的 `accept()` 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于“函数式编程”一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 `CommunicateA.perform()` 。 +`CommunicateA.perform()` 在没有约束的 **P** 上生成。 只要可以使用 `Consumer

`,它在这里就可以是任何东西,这些 `Consumer

` 代表不带参数的 **P** 方法的未绑定方法引用。当您调用 **Consumer** 的 `accept()` 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于 [函数式编程](book/13-Functional-Programming.md) 一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 `CommunicateA.perform()` 。 之所以称其为“辅助”,是因为您必须显式地为 `perform()` 提供要使用的方法引用。 它不能只按名称调用方法。 尽管传递未绑定的方法引用似乎要花很多力气,但潜在类型的最终目标还是可以实现的。 我们创建了一个代码片段 `CommunicateA.perform()` ,该代码可用于任何具有符合签名的方法引用的类型。 请注意,这与我们看到的其他语言中的潜在类型有所不同,因为这些语言不仅需要签名以符合规范,还需要方法名称。 因此,该技术可以说产生了更多的通用代码。 -为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime** 。 +为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime**。 ### 使用**Suppliers**类的通用方法 -通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的实用程序方法。 “通用化”这些操作很有意义: +通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的工具方法。 泛化这些操作很有意义: ```java // onjava/Suppliers.java @@ -4922,38 +5031,40 @@ import java.util.function.*; import java.util.stream.*; public class Suppliers { - // Create a collection and fill it: - public static > C - create(Supplier factory, Supplier gen, int n) { - return Stream.generate(gen) - .limit(n) - .collect(factory, C::add, C::addAll); - } - // Fill an existing collection: - public static > - C fill(C coll, Supplier gen, int n) { - Stream.generate(gen) - .limit(n) - .forEach(coll::add); - return coll; - } - // Use an unbound method reference to - // produce a more general method: - public static H fill(H holder, - BiConsumer adder, Supplier gen, int n) { - Stream.generate(gen) - .limit(n) - .forEach(a -> adder.accept(holder, a)); - return holder; - } + // Create a collection and fill it: + public static > C + create(Supplier factory, Supplier gen, int n) { + return Stream.generate(gen) + .limit(n) + .collect(factory, C::add, C::addAll); + } + + // Fill an existing collection: + public static > + C fill(C coll, Supplier gen, int n) { + Stream.generate(gen) + .limit(n) + .forEach(coll::add); + return coll; + } + + // Use an unbound method reference to + // produce a more general method: + public static H fill(H holder, + BiConsumer adder, Supplier gen, int n) { + Stream.generate(gen) + .limit(n) + .forEach(a -> adder.accept(holder, a)); + return holder; + } } ``` `create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。 -前两种方法一般都受约束以与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数a调用对象 **holder** 上的未绑定方法 **holder**。 +前两种方法一般都受约束,只能与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数 a 调用对象 **holder** 上的未绑定方法 **holder**。 -在一个稍作模拟的测试中对 **Suppliers** 实用程序进行了测试,该仿真还使用了本章前面定义的 **RandomList** : +在一个稍作模拟的测试中对 **Suppliers** 工具程序进行了测试,该仿真还使用了本章前面定义的 **RandomList** : ```java // generics/BankTeller.java @@ -4963,53 +5074,53 @@ import java.util.*; import onjava.*; class Customer { - private static long counter = 1; - private final long id = counter++; - @Override - public String toString() { - return "Customer " + id; - } + private static long counter = 1; + private final long id = counter++; + @Override + public String toString() { + return "Customer " + id; + } } class Teller { - private static long counter = 1; - private final long id = counter++; - @Override - public String toString() { - return "Teller " + id; - } + private static long counter = 1; + private final long id = counter++; + @Override + public String toString() { + return "Teller " + id; + } } class Bank { - private List tellers = - new ArrayList<>(); - public void put(BankTeller bt) { - tellers.add(bt); - } + private List tellers = + new ArrayList<>(); + public void put(BankTeller bt) { + tellers.add(bt); + } } public class BankTeller { - public static void serve(Teller t, Customer c) { - System.out.println(t + " serves " + c); - } - public static void main(String[] args) { - // Demonstrate create(): - RandomList tellers = - Suppliers.create( - RandomList::new, Teller::new, 4); - // Demonstrate fill(): - List customers = Suppliers.fill( - new ArrayList<>(), Customer::new, 12); - customers.forEach(c -> - serve(tellers.select(), c)); - // Demonstrate assisted latent typing: - Bank bank = Suppliers.fill( - new Bank(), Bank::put, BankTeller::new, 3); - // Can also use second version of fill(): - List customers2 = Suppliers.fill( - new ArrayList<>(), - List::add, Customer::new, 12); - } + public static void serve(Teller t, Customer c) { + System.out.println(t + " serves " + c); + } + public static void main(String[] args) { + // Demonstrate create(): + RandomList tellers = + Suppliers.create( + RandomList::new, Teller::new, 4); + // Demonstrate fill(): + List customers = Suppliers.fill( + new ArrayList<>(), Customer::new, 12); + customers.forEach(c -> + serve(tellers.select(), c)); + // Demonstrate assisted latent typing: + Bank bank = Suppliers.fill( + new Bank(), Bank::put, BankTeller::new, 3); + // Can also use second version of fill(): + List customers2 = Suppliers.fill( + new ArrayList<>(), + List::add, Customer::new, 12); + } } /* Output: Teller 3 serves Customer 1 @@ -5027,41 +5138,50 @@ Teller 4 serves Customer 12 */ ``` -可以看到 `create()` 生成一个新的 **Collection** 对象,而 `fill()` 添加到现有 **Collection** 中。第二个版本`fill()` 显示,它不仅与新的和无关的类型 **Bank** 一起使用,还与 **List** 一起使用。因此,从技术上讲,`fill()` 的第一个版本在技术上不是必需的,但在使用 **Collection** 时提供了较短的语法。 +可以看到 `create()` 生成一个新的 **Collection** 对象,而 `fill()` 添加到现有 **Collection** 中。第二个版本`fill()` 显示,它不仅与无关的新类型 **Bank** 一起使用,还能与 **List** 一起使用。因此,从技术上讲,`fill()` 的第一个版本在技术上不是必需的,但在使用 **Collection** 时提供了较短的语法。 ## 总结:类型转换真的如此之糟吗? -自从C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。 +自从 C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。 -这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用容器类的地方,这些类包括诸如各种 **List** 、各种 **Set** 、各种 **Map** 等你在集合章节和附件:集合主题章节中看到的各种类。在 Java SE 5 之前,当你将一个对象放置到容器中时,这个对象就会被向上转型为 **Object** ,因此你会丢失类型信息。当你想要将这个对象从容器中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。我用的示例是持有 **Cat** 的 **List** (这个示例的一种使用苹果和桔子的变体在集合章节的开头展示过)。如果没有 Java SE 5 的泛型版本的容器,你放到容器里的和从容器中取回的,都是 **Object** 。因此,我们很可能会将一个 **Dog** 放置到 **Cat** 的 **List** 中。 +这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用集合类的地方,这些类包括诸如各种 **List** 、各种 **Set** 、各种 **Map** 等你在 [集合](book/12-Collections.md) 和 [附录:集合主题](book/Appendix-Collection-Topics.md) 这两章所见。在 Java 5 之前,当你将一个对象放置到集合中时,这个对象就会被向上转型为 **Object** ,因此你会丢失类型信息。当你想要将这个对象从集合中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。我用的示例是持有 **Cat** 的 **List** (这个示例的一种使用苹果和桔子的变体在 [集合](book/12-Collections.md) 章节的开头展示过)。如果没有 Java 5 泛型版本的集合,你放到容集里和从集合中取回的都是 **Object** 。因此,我们很可能会将一个 **Dog** 放置到 **Cat** 的 **List** 中。 -但是,泛型出现之前的 Java 并不会让你误用放入到容器中的对象。如果将一个 **Dog** 扔到 **Cat** 的容器中,并且试图将这个容器中的所有东西都当作 **Cat** 处理,那么当你从这个 **Cat** 容器中取回那个 **Dog** 引用,并试图将其转型为**Cat** 时,就会得到一个 **RuntimeException** 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。 +但是,泛型出现之前的 Java 并不会让你误用放入到集合中的对象。如果将一个 **Dog** 扔到 **Cat** 的集合中,并且试图将这个集合中的所有东西都当作 **Cat** 处理,那么当你从这个 **Cat** 集合中取回那个 **Dog** 引用,并试图将其转型为 **Cat** 时,就会得到一个 **RuntimeException** 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。 在本书以前的版本中,我曾经说过: -> 这不止是令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向容器中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了容器中,那么必须发现这个不良插入到底是在何处发生的。 +> 这不止令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向集合中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了集合中,那么必须发现这个不良插入到底是在何处发生的。 > -但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个示例,它是一个包含 **String** 对象的被称为 **files** 的列表在这个示例中,向 **files** 中添加一个 **File** 对象看起来相当自然,因此这个对象的名字可能叫 **fileNames** 更好。无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的容器,例如 **cats** ,因为它们可以向试图添加非 **Cat** 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。 +但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个称为 **files** 的 list 示例,它包含 **String** 对象。在这个示例中,向 **files** 中添加一个 **File** 对象看起来相当自然,因此这个对象的名字可能叫 **fileNames** 更好。无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的集合,例如 **cats** ,因为它们可以向试图添加非 **Cat** 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。 -有一位作者甚至断言,这样的缺陷将“*潜伏数年*”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在第多线程编程章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗? -我相信被称为*泛型*的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的容器。类型安全的容器是能够创建更通用代码这一能力所带来的副作用。 -因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解适配器设计模式的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。 +有一位作者甚至断言,这样的缺陷将“*潜伏数年*”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在 [多线程编程](book/24-Concurrent-Programming.md) 章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗? +我相信被称为*泛型*的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的集合。类型安全的集合是能够创建更通用代码这一能力所带来的副作用。 +因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解这些代码的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。 还要注意到,因为泛型是后来添加到 Java 中,而不是从一开始就设计到这种语言中的,所以某些容器无法达到它们应该具备的健壮性。例如,观察一下 **Map** ,在特定的方法 `containsKey(Object key) `和 `get(Object key)` 中就包含这类情况。如果这些类是使用在它们之前就存在的泛型设计的,那么这些方法将会使用参数化类型而不是 **Object** ,因此也就可以提供这些泛型假设会提供的编译期检查。例如,在 C++ 的 **map** 中,键的类型总是在编译期检查的。 + 有一件事很明显:在一种语言已经被广泛应用之后,在其较新的版本中引入任何种类的泛型机制,都会是一项非常非常棘手的任务,并且是一项不付出艰辛就无法完成的任务。在 C++ 中,模版是在其最初的 ISO 版本中就引入的(即便如此,也引发了阵痛,因为在第一个标准 C++ 出现之前,有很多非模版版本在使用),因此实际上模版一直都是这种语言的一部分。在 Java 中,泛型是在这种语言首次发布大约 10 年之后才引入的,因此向泛型迁移的问题特别多,并且对泛型的设计产生了明显的影响。其结果就是,程序员将承受这些痛苦,而这一切都是由于 Java 设计者在设计 1.0 版本时所表现出来的短视造成的。当 Java 最初被创建时,它的设计者们当然了解 C++ 的模版,他们甚至考虑将其囊括到 Java 语言中,但是出于这样或那样的原因,他们决定将模版排除在外(其迹象就是他们过于匆忙)。因此, Java 语言和使用它的程序员都将承受这些痛苦。只有时间将会说明 Java 的泛型方式对这种语言所造成的最终影响。 -某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语句将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。 +某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语言将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。 ## 进阶阅读 +泛型的入门文档是 《Generics in the Java Programming Language》,作者是 Gilad Bracha,可以从 http://java.oracle.com 获取。 + +Angelika Langer 的《Java Generics FAQs》是一份非常有帮助的资料,可以从 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html。 + +你可以从 《Adding Wildcards to the Java Programming Language》中学到更多关于通配符的知识,作者是 Torgerson、Ernst、Hansen、von der Ahe、Bracha 和 Gafter,地址是 http://www.jot.fm/issues/issue_2004_12/article5。 + +Neal After 对于 Java 问题(尤其是擦除)的看法可以从这里找到:http://www.infoq.com/articles/neal-gafter-on-java。 + [^1]: 在编写本章期间,Angelika Langer的 Java 泛型常见问题解答以及她的其他著作(与Klaus Kreft一起)是非常宝贵的。 [^2]: [http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html](http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html) [^3]: 参见本章章末引文。 - - - +[^4]: 注意,一些编程环境,如 Eclipse 和 IntelliJ IDEA,将会自动生成委托代码。 +[^5]: 因为可以使用转型,有效地禁止了类型系统,一些人就认为 C++ 是弱类型,但这太极端了。一种可能更好的说法是 C++ 是有一道暗门的强类型语言。 +[^6]: 我再次从 Brian Goetz 那获得帮助。

\ No newline at end of file