diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 984f4f34..c0e48e0f 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -570,6 +570,520 @@ extends Fibonacci implements Iterable { ## 泛型方法 +到目前为止,我们已经研究了参数化整个类。其实还可以参数化类中的方法。类本身可能是泛型的,也可能不是,不过这与它的方法是否是泛型的并没有什么关系。 + +泛型方法独立于类而改变方法。作为准则,请“尽可能”使用泛型方法。通常将单个方法泛型化要比将整个类泛型化更清晰易懂。 + +如果方法是 **static** 的,则无法访问该类的泛型类型参数,因此,如果使用了泛型类型参数,则它必须是泛型方法。 + +要定义泛型方法,请将泛型参数列表放置在返回值之前,如下所示: + +```java +// generics/GenericMethods.java + +public class GenericMethods { + public void f(T x) { + System.out.println(x.getClass().getName()); + } + + public static void main(String[] args) { + GenericMethods gm = new GenericMethods(); + gm.f(""); + gm.f(1); + gm.f(1.0); + gm.f(1.0F); + gm.f('c'); + gm.f(gm); + } +} +/* Output: +java.lang.String +java.lang.Integer +java.lang.Double +java.lang.Float +java.lang.Character +GenericMethods +*/ +``` + +尽管可以同时对类及其方法进行参数化,但这里未将 **GenericMethods** 类参数化。只有方法 `f()` 具有类型参数,该参数由方法返回类型之前的参数列表指示。 + +对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 *类型参数推断*。因此,对`f()` 的调用看起来像普通的方法调用,并且 `f()` 看起来像被重载了无数次一样。它甚至会接受 **GenericMethods** 类型的参数。 + +如果使用基本类型调用 `f()` ,自动装箱就开始起作用,自动将基本类型包装在它们对应的包装类型中。 + + +### 变量和泛型方法 + +泛型方法和变长参数列表可以很好地共存: + +```java +// generics/GenericVarargs.java + +import java.util.ArrayList; +import java.util.List; + +public class GenericVarargs { + @SafeVarargs + public static List makeList(T... args) { + List result = new ArrayList<>(); + for (T item : args) + result.add(item); + return result; + } + + public static void main(String[] args) { + List ls = makeList("A"); + System.out.println(ls); + ls = makeList("A", "B", "C"); + System.out.println(ls); + ls = makeList( + "ABCDEFFHIJKLMNOPQRSTUVWXYZ".split("")); + System.out.println(ls); + } +} +/* Output: +[A] +[A, B, C] +[A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, +S, T, U, V, W, X, Y, Z] +*/ +``` + +此处显示的 `makeList()` 方法产生的功能与标准库的 `java.util.Arrays.asList()` 方法相同。 + +`@SafeVarargs` 注解保证我们不会对变长参数列表进行任何修改,这是正确的,因为我们只从中读取。如果没有此注解,编译器将无法知道这些并会发出警告。 + + +### 一个泛型的Supplier + +这是一个为任意具有无参构造方法的类生成 **Supplier** 的类。为了减少键入,它还包括一个用于生成 **BasicSupplier** 的泛型方法: + +```java +// onjava/BasicSupplier.java +// Supplier from a class with a no-arg constructor +package onjava; + +import java.util.function.Supplier; + +public class BasicSupplier implements Supplier { + private Class type; + + public BasicSupplier(Class type) { + this.type = type; + } + + @Override + public T get() { + try { + // Assumes type is a public class: + return type.newInstance(); + } catch (InstantiationException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + // Produce a default Supplier from a type token: + public static Supplier create(Class type) { + return new BasicSupplier<>(type); + } +} +``` + +此类提供了产生以下对象的基本实现: + +1. 是 **public** 的。 因为 **BasicSupplier** 在单独的包中,所以相关的类必须具有 **public** 权限,而不仅仅是包级访问权限。 + +2. 具有无参构造方法。要创建一个这样的 **BasicSupplier** 对象,请调用 `create()` 方法,并将要生成类型的类型令牌传递给它。通用的 `create()` 方法提供了 `BasicSupplier.create(MyType.class)` 这种较简洁的语法来代替较笨拙的 `new BasicSupplier (MyType.class)`。 + +例如,这是一个具有无参构造方法的简单类: + +```java +// generics/CountedObject.java + +public class CountedObject { + private static long counter = 0; + private final long id = counter++; + + public long id() { + return id; + } + + @Override + public String toString() { + return "CountedObject " + id; + } +} +``` + +**CountedObject** 类可以跟踪自身创建了多少个实例,并通过 `toString()` 报告这些实例的数量。 **BasicSupplier** 可以轻松地为 **CountedObject** 创建 **Supplier**: + +```java + // generics/BasicSupplierDemo.java + +import onjava.BasicSupplier; + +import java.util.stream.Stream; + +public class BasicSupplierDemo { + public static void main(String[] args) { + Stream.generate( + BasicSupplier.create(CountedObject.class)) + .limit(5) + .forEach(System.out::println); + } +} +/* Output: +CountedObject 0 +CountedObject 1 +CountedObject 2 +CountedObject 3 +CountedObject 4 +*/ +``` + +泛型方法减少了产生 **Supplier** 对象所需的代码量。 Java 泛型强制传递 **Class** 对象,以便在 `create()` 方法中将其用于类型推断。 + + +### 简化元组的使用 + +使用类型参数推断和静态导入,我们将把早期的元组重写为更通用的库。在这里,我们使用重载的静态方法创建元组: + +```java +// onjava/Tuple.java +// Tuple library using type argument inference +package onjava; + +public class Tuple { + public static Tuple2 tuple(A a, B b) { + return new Tuple2<>(a, b); + } + + public static Tuple3 + tuple(A a, B b, C c) { + return new Tuple3<>(a, b, c); + } + + public static Tuple4 + tuple(A a, B b, C c, D d) { + return new Tuple4<>(a, b, c, d); + } + + public static + Tuple5 tuple(A a, B b, C c, D d, E e) { + return new Tuple5<>(a, b, c, d, e); + } +} +``` + +我们修改 **TupleTest.java** 来测试 **Tuple.java** : + +```java +// generics/TupleTest2.java + +import onjava.Tuple2; +import onjava.Tuple3; +import onjava.Tuple4; +import onjava.Tuple5; + +import static onjava.Tuple.tuple; + +public class TupleTest2 { + static Tuple2 f() { + return tuple("hi", 47); + } + + static Tuple2 f2() { + return tuple("hi", 47); + } + + static Tuple3 g() { + return tuple(new Amphibian(), "hi", 47); + } + + static Tuple4 h() { + return tuple( + new Vehicle(), new Amphibian(), "hi", 47); + } + + static Tuple5 k() { + return tuple(new Vehicle(), new Amphibian(), + "hi", 47, 11.1); + } + + public static void main(String[] args) { + Tuple2 ttsi = f(); + System.out.println(ttsi); + System.out.println(f2()); + System.out.println(g()); + System.out.println(h()); + System.out.println(k()); + } +} +/* Output: +(hi, 47) +(hi, 47) +(Amphibian@14ae5a5, hi, 47) +(Vehicle@135fbaa4, Amphibian@45ee12a7, hi, 47) +(Vehicle@4b67cf4d, Amphibian@7ea987ac, hi, 47, 11.1) +*/ +``` + +请注意,`f()` 返回一个参数化的 **Tuple2** 对象,而 `f2()` 返回一个未参数化的 **Tuple2** 对象。编译器不会在这里警告 `f2()` ,因为返回值未以参数化方式使用。从某种意义上说,它被“向上转型”为一个未参数化的 **Tuple2** 。 但是,如果如果尝试将 `f2()` 的结果放入到参数化的 **Tuple2** 中,则编译器将发出警告。 + + +### 一个Set工具 + +对于泛型方法的另一个示例,请考虑由 **Set** 表示的数学关系。这些被方便地定义为可用于所有不同类型的泛型方法: + +```java +// onjava/Sets.java + +package onjava; + +import java.util.HashSet; +import java.util.Set; + +public class Sets { + public static Set union(Set a, Set b) { + Set result = new HashSet<>(a); + result.addAll(b); + return result; + } + + public static + Set intersection(Set a, Set b) { + Set result = new HashSet<>(a); + result.retainAll(b); + return result; + } + + // Subtract subset from superset: + public static Set + difference(Set superset, Set subset) { + Set result = new HashSet<>(superset); + result.removeAll(subset); + return result; + } + + // Reflexive--everything not in the intersection: + public static Set complement(Set a, Set b) { + return difference(union(a, b), intersection(a, b)); + } +} +``` + +前三个方法通过将第一个参数的引用复制到新的 **HashSet** 对象中来复制第一个参数,因此不会直接修改参数集合。因此,返回值是一个新的 **Set** 对象。 + +这四种方法代表数学集合操作: `union()` 返回一个包含两个参数并集的 **Set** , `intersection()` 返回一个包含两个参数集合交集的 **Set** , `difference()` 从 **superset** 中减去 **subset** 的元素 ,而 `complement()` 返回所有不在交集中的元素的 **Set**。作为显示这些方法效果的简单示例的一部分,下面是一个包含不同水彩名称的 **enum** : + +```java +// generics/watercolors/Watercolors.java + +package watercolors; + +public enum Watercolors { + ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW, + ORANGE, BRILLIANT_RED, CRIMSON, MAGENTA, + ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE, + PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, + PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, + YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, + BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK +} +``` + +为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](https://github.com/LingCoder/OnJava8/blob/master/docs/book/22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: + +```java +// generics/WatercolorSets.java + +import watercolors.*; + +import java.util.EnumSet; +import java.util.Set; + +import static watercolors.Watercolors.*; +import static onjava.Sets.*; + +public class WatercolorSets { + public static void main(String[] args) { + Set set1 = + EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE); + Set set2 = + EnumSet.range(CERULEAN_BLUE_HUE, BURNT_UMBER); + System.out.println("set1: " + set1); + System.out.println("set2: " + set2); + System.out.println( + "union(set1, set2): " + union(set1, set2)); + Set subset = intersection(set1, set2); + System.out.println( + "intersection(set1, set2): " + subset); + System.out.println("difference(set1, subset): " + + difference(set1, subset)); + System.out.println("difference(set2, subset): " + + difference(set2, subset)); + System.out.println("complement(set1, set2): " + + complement(set1, set2)); + } +} +/* Output: +set1: [BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, +VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, +COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE] +set2: [CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, +COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, +SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, +BURNT_UMBER] +union(set1, set2): [BURNT_SIENNA, BRILLIANT_RED, +YELLOW_OCHRE, MAGENTA, SAP_GREEN, CERULEAN_BLUE_HUE, +ULTRAMARINE, VIRIDIAN_HUE, VIOLET, RAW_UMBER, +ROSE_MADDER, PERMANENT_GREEN, BURNT_UMBER, +PHTHALO_BLUE, CRIMSON, COBALT_BLUE_HUE] +intersection(set1, set2): [PERMANENT_GREEN, +CERULEAN_BLUE_HUE, ULTRAMARINE, VIRIDIAN_HUE, +PHTHALO_BLUE, COBALT_BLUE_HUE] +difference(set1, subset): [BRILLIANT_RED, MAGENTA, +VIOLET, CRIMSON, ROSE_MADDER] +difference(set2, subset): [BURNT_SIENNA, YELLOW_OCHRE, +BURNT_UMBER, SAP_GREEN, RAW_UMBER] +complement(set1, set2): [BURNT_SIENNA, BRILLIANT_RED, +YELLOW_OCHRE, MAGENTA, SAP_GREEN, VIOLET, RAW_UMBER, +ROSE_MADDER, BURNT_UMBER, CRIMSON] +*/ +``` + +接下来的例子使用 `Sets.difference()` 方法来展示 **java.util** 包中各种 **Collection** 和 **Map** 类之间的方法差异: + +```java +// onjava/CollectionMethodDifferences.java +// {java onjava.CollectionMethodDifferences} + +package onjava; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.stream.Collectors; + +public class CollectionMethodDifferences { + static Set methodSet(Class type) { + return Arrays.stream(type.getMethods()) + .map(Method::getName) + .collect(Collectors.toCollection(TreeSet::new)); + } + + static void interfaces(Class type) { + System.out.print("Interfaces in " + + type.getSimpleName() + ": "); + System.out.println( + Arrays.stream(type.getInterfaces()) + .map(Class::getSimpleName) + .collect(Collectors.toList())); + } + + static Set object = methodSet(Object.class); + + static { + object.add("clone"); + } + + static void + difference(Class superset, Class subset) { + System.out.print(superset.getSimpleName() + + " extends " + subset.getSimpleName() + + ", adds: "); + Set comp = Sets.difference( + methodSet(superset), methodSet(subset)); + comp.removeAll(object); // Ignore 'Object' methods + System.out.println(comp); + interfaces(superset); + } + + public static void main(String[] args) { + System.out.println("Collection: " + + methodSet(Collection.class)); + interfaces(Collection.class); + difference(Set.class, Collection.class); + difference(HashSet.class, Set.class); + difference(LinkedHashSet.class, HashSet.class); + difference(TreeSet.class, Set.class); + difference(List.class, Collection.class); + difference(ArrayList.class, List.class); + difference(LinkedList.class, List.class); + difference(Queue.class, Collection.class); + difference(PriorityQueue.class, Queue.class); + System.out.println("Map: " + methodSet(Map.class)); + difference(HashMap.class, Map.class); + difference(LinkedHashMap.class, HashMap.class); + difference(SortedMap.class, Map.class); + difference(TreeMap.class, Map.class); + } +} +/* Output: +Collection: [add, addAll, clear, contains, containsAll, +equals, forEach, hashCode, isEmpty, iterator, +parallelStream, remove, removeAll, removeIf, retainAll, +size, spliterator, stream, toArray] +Interfaces in Collection: [Iterable] +Set extends Collection, adds: [] +Interfaces in Set: [Collection] +HashSet extends Set, adds: [] +Interfaces in HashSet: [Set, Cloneable, Serializable] +LinkedHashSet extends HashSet, adds: [] +Interfaces in LinkedHashSet: [Set, Cloneable, +Serializable] +TreeSet extends Set, adds: [headSet, +descendingIterator, descendingSet, pollLast, subSet, +floor, tailSet, ceiling, last, lower, comparator, +pollFirst, first, higher] +Interfaces in TreeSet: [NavigableSet, Cloneable, +Serializable] +List extends Collection, adds: [replaceAll, get, +indexOf, subList, set, sort, lastIndexOf, listIterator] +Interfaces in List: [Collection] +ArrayList extends List, adds: [trimToSize, +ensureCapacity] +Interfaces in ArrayList: [List, RandomAccess, +Cloneable, Serializable] +LinkedList extends List, adds: [offerFirst, poll, +getLast, offer, getFirst, removeFirst, element, +removeLastOccurrence, peekFirst, peekLast, push, +pollFirst, removeFirstOccurrence, descendingIterator, +pollLast, removeLast, pop, addLast, peek, offerLast, +addFirst] +Interfaces in LinkedList: [List, Deque, Cloneable, +Serializable] +Queue extends Collection, adds: [poll, peek, offer, +element] +Interfaces in Queue: [Collection] +PriorityQueue extends Queue, adds: [comparator] +Interfaces in PriorityQueue: [Serializable] +Map: [clear, compute, computeIfAbsent, +computeIfPresent, containsKey, containsValue, entrySet, +equals, forEach, get, getOrDefault, hashCode, isEmpty, +keySet, merge, put, putAll, putIfAbsent, remove, +replace, replaceAll, size, values] +HashMap extends Map, adds: [] +Interfaces in HashMap: [Map, Cloneable, Serializable] +LinkedHashMap extends HashMap, adds: [] +Interfaces in LinkedHashMap: [Map] +SortedMap extends Map, adds: [lastKey, subMap, +comparator, firstKey, headMap, tailMap] +Interfaces in SortedMap: [Map] +TreeMap extends Map, adds: [descendingKeySet, +navigableKeySet, higherEntry, higherKey, floorKey, +subMap, ceilingKey, pollLastEntry, firstKey, lowerKey, +headMap, tailMap, lowerEntry, ceilingEntry, +descendingMap, pollFirstEntry, lastKey, firstEntry, +floorEntry, comparator, lastEntry] +Interfaces in TreeMap: [NavigableMap, Cloneable, +Serializable] +*/ +``` + +在[第十二章 集合](./12-Collections.md)的[本章小结](./12-Collections.md#本章小结)部分将会用到这里的输出结果。 ## 复杂模型构建