diff --git a/docs/book/Appendix-Collection-Topics.md b/docs/book/Appendix-Collection-Topics.md index f1b3dcc..8123c28 100644 --- a/docs/book/Appendix-Collection-Topics.md +++ b/docs/book/Appendix-Collection-Topics.md @@ -362,23 +362,453 @@ Cyan 可以看到,使用 **LinkedHashMap** 确实能够保留 **HTMLColors.ARRAY** 的顺序。 -## List表现 +## List行为 +**Lists** 是存储和检索对象(次于数组)的最基本方法。基本列表操作包括: + +- **add()** 用于插入元素 +- **get()** 用于随机访问元素 +- **iterator()** 获取序列上的一个 **Iterator** +- **stream()** 生成元素的一个 **Stream** + +列表构造方法始终保留元素的添加顺序。 + +以下示例中的方法各自涵盖了一组不同的行为:每个 **List** 可以执行的操作( **basicTest()** ),使用 **Iterator** ( **iterMotion()** )遍历序列,使用 **Iterator** ( **iterManipulation()** )更改内容,查看 **List** 操作( **testVisual()** )的效果,以及仅可用于 **LinkedLists** 的操作: + +```java +// collectiontopics/ListOps.java +// Things you can do with Lists +import java.util.*; +import onjava.HTMLColors; + +public class ListOps { + // Create a short list for testing: + static final List LIST = + HTMLColors.LIST.subList(0, 10); + private static boolean b; + private static String s; + private static int i; + private static Iterator it; + private static ListIterator lit; + public static void basicTest(List a) { + a.add(1, "x"); // Add at location 1 + a.add("x"); // Add at end + // Add a collection: + a.addAll(LIST); + // Add a collection starting at location 3: + a.addAll(3, LIST); + b = a.contains("1"); // Is it in there? + // Is the entire collection in there? + b = a.containsAll(LIST); + // Lists allow random access, which is cheap + // for ArrayList, expensive for LinkedList: + s = a.get(1); // Get (typed) object at location 1 + i = a.indexOf("1"); // Tell index of object + b = a.isEmpty(); // Any elements inside? + it = a.iterator(); // Ordinary Iterator + lit = a.listIterator(); // ListIterator + lit = a.listIterator(3); // Start at location 3 + i = a.lastIndexOf("1"); // Last match + a.remove(1); // Remove location 1 + a.remove("3"); // Remove this object + a.set(1, "y"); // Set location 1 to "y" + // Keep everything that's in the argument + // (the intersection of the two sets): + a.retainAll(LIST); + // Remove everything that's in the argument: + a.removeAll(LIST); + i = a.size(); // How big is it? + a.clear(); // Remove all elements + } + public static void iterMotion(List a) { + ListIterator it = a.listIterator(); + b = it.hasNext(); + b = it.hasPrevious(); + s = it.next(); + i = it.nextIndex(); + s = it.previous(); + i = it.previousIndex(); + } + public static void iterManipulation(List a) { + ListIterator it = a.listIterator(); + it.add("47"); + // Must move to an element after add(): + it.next(); + // Remove the element after the new one: + it.remove(); + // Must move to an element after remove(): + it.next(); + // Change the element after the deleted one: + it.set("47"); + } + public static void testVisual(List a) { + System.out.println(a); + List b = LIST; + System.out.println("b = " + b); + a.addAll(b); + a.addAll(b); + System.out.println(a); + // Insert, remove, and replace elements + // using a ListIterator: + ListIterator x = + a.listIterator(a.size()/2); + x.add("one"); + System.out.println(a); + System.out.println(x.next()); + x.remove(); + System.out.println(x.next()); + x.set("47"); + System.out.println(a); + // Traverse the list backwards: + x = a.listIterator(a.size()); + while(x.hasPrevious()) + System.out.print(x.previous() + " "); + System.out.println(); + System.out.println("testVisual finished"); + } + // There are some things that only LinkedLists can do: + public static void testLinkedList() { + LinkedList ll = new LinkedList<>(); + ll.addAll(LIST); + System.out.println(ll); + // Treat it like a stack, pushing: + ll.addFirst("one"); + ll.addFirst("two"); + System.out.println(ll); + // Like "peeking" at the top of a stack: + System.out.println(ll.getFirst()); + // Like popping a stack: + System.out.println(ll.removeFirst()); + System.out.println(ll.removeFirst()); + // Treat it like a queue, pulling elements + // off the tail end: + System.out.println(ll.removeLast()); + System.out.println(ll); + } + public static void main(String[] args) { + // Make and fill a new list each time: + basicTest(new LinkedList<>(LIST)); + basicTest(new ArrayList<>(LIST)); + iterMotion(new LinkedList<>(LIST)); + iterMotion(new ArrayList<>(LIST)); + iterManipulation(new LinkedList<>(LIST)); + iterManipulation(new ArrayList<>(LIST)); + testVisual(new LinkedList<>(LIST)); + testLinkedList(); + } +} +/* Output: +[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet] +b = [AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet] +[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet, +AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet, +AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet] +[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet, +AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, one, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet, +AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet] +Bisque +Black +[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet, +AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, one, +47, BlanchedAlmond, Blue, BlueViolet, AliceBlue, +AntiqueWhite, Aquamarine, Azure, Beige, Bisque, Black, +BlanchedAlmond, Blue, BlueViolet] +BlueViolet Blue BlanchedAlmond Black Bisque Beige Azure +Aquamarine AntiqueWhite AliceBlue BlueViolet Blue +BlanchedAlmond 47 one Beige Azure Aquamarine +AntiqueWhite AliceBlue BlueViolet Blue BlanchedAlmond +Black Bisque Beige Azure Aquamarine AntiqueWhite +AliceBlue +testVisual finished +[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue, BlueViolet] +[two, one, AliceBlue, AntiqueWhite, Aquamarine, Azure, +Beige, Bisque, Black, BlanchedAlmond, Blue, BlueViolet] +two +two +one +BlueViolet +[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, +Bisque, Black, BlanchedAlmond, Blue] +*/ +``` + +在 **basicTest()** 和 **iterMotion()** 中,方法调用是为了展示正确的语法,尽管获取了返回值,但不会使用它。在某些情况下,根本不会去获取返回值。在使用这些方法之前,请查看JDK文档中这些方法的完整用法。 -## Set表现 +## Set行为 +**Set** 的主要用处是测试成员身份,不过也可以将其用作删除重复元素的工具。如果不关心元素顺序或并发性, **HashSet** 总是最好的选择,因为它是专门为了快速查找而设计的(这里使用了在[附录:理解equals和hashCode方法]()章节中探讨的散列函数)。 + +其它的 **Set** 实现产生不同的排序行为: + +```java +// collectiontopics/SetOrder.java +import java.util.*; +import onjava.HTMLColors; + +public class SetOrder { + static String[] sets = { + "java.util.HashSet", + "java.util.TreeSet", + "java.util.concurrent.ConcurrentSkipListSet", + "java.util.LinkedHashSet", + "java.util.concurrent.CopyOnWriteArraySet", + }; + static final List RLIST = + new ArrayList<>(HTMLColors.LIST); + static { + Collections.reverse(RLIST); + } + public static void + main(String[] args) throws Exception { + for(String type: sets) { + System.out.format("[-> %s <-]%n", + type.substring(type.lastIndexOf('.') + 1)); + @SuppressWarnings("unchecked") + Set set = (Set) + Class.forName(type).newInstance(); + set.addAll(RLIST); + set.stream() + .limit(10) + .forEach(System.out::println); + } + } +} +/* Output: +[-> HashSet <-] +MediumOrchid +PaleGoldenRod +Sienna +LightSlateGray +DarkSeaGreen +Black +Gainsboro +Orange +LightCoral +DodgerBlue +[-> TreeSet <-] +AliceBlue +AntiqueWhite +Aquamarine +Azure +Beige +Bisque +Black +BlanchedAlmond +Blue +BlueViolet +[-> ConcurrentSkipListSet <-] +AliceBlue +AntiqueWhite +Aquamarine +Azure +Beige +Bisque +Black +BlanchedAlmond +Blue +BlueViolet +[-> LinkedHashSet <-] +YellowGreen +Yellow +WhiteSmoke +White +Wheat +Violet +Turquoise +Tomato +Thistle +Teal +[-> CopyOnWriteArraySet <-] +YellowGreen +Yellow +WhiteSmoke +White +Wheat +Violet +Turquoise +Tomato +Thistle +Teal +*/ +``` + +这里需要使用 **@SuppressWarnings(“unchecked”)** ,因为这里将一个 **String** (可能是任何东西)传递给了 **Class.forName(type).newInstance()** 。编译器并不能保证这是一次成功的操作。 + +**RLIST** 是 **HTMLColors.LIST** 的反转版本。因为 **Collections.reverse()** 是通过修改参数来执行反向操作,而不是返回包含反向元素的新 **List** ,所以该调用在 **static** 块内执行。 **RLIST** 可以防止我们意外地认为 **Set** 对其结果进行了排序。 + +**HashSet** 的输出结果似乎没有可辨别的顺序,因为它是基于散列函数的。 **TreeSet** 和 **ConcurrentSkipListSet** 都对它们的元素进行了排序,它们都实现了 **SortedSet** 接口来标识这个特点。因为实现该接口的 **Set** 按顺序排列,所以该接口还有一些其他的可用操作。 **LinkedHashSet** 和 **CopyOnWriteArraySet** 尽管没有用于标识的接口,但它们还是保留了元素的插入顺序。 + +**ConcurrentSkipListSet** 和 **CopyOnWriteArraySet** 是线程安全的。 + +在附录的最后,我们将了解在非 **HashSet** 实现的 **Set** 上添加额外排序的性能成本,以及不同实现中的任何其他功能的成本。 ## 在Map中使用函数式操作 +与 **Collection** 接口一样,**forEach()** 也内置在 **Map** 接口中。但是如果想要执行任何其他的基本功能操作,比如 **map()** ,**flatMap()** ,**reduce()** 或 **filter()** 时,该怎么办? 查看 **Map** 接口发现并没有这些。 + +可以通过 **entrySet()** 连接到这些方法,该方法会生成一个由 **Map.Entry** 对象组成的 **Set** 。这个 **Set** 包含 **stream()** 和 **parallelStream()** 方法。只需要记住一件事,这里正在使用的是 **Map.Entry** 对象: + +```java +// collectiontopics/FunctionalMap.java +// Functional operations on a Map +import java.util.*; +import java.util.stream.*; +import java.util.concurrent.*; +import static onjava.HTMLColors.*; + +public class FunctionalMap { + public static void main(String[] args) { + MAP.entrySet().stream() + .map(Map.Entry::getValue) + .filter(v -> v.startsWith("Dark")) + .map(v -> v.replaceFirst("Dark", "Hot")) + .forEach(System.out::println); + } +} +/* Output: +HotBlue +HotCyan +HotGoldenRod +HotGray +HotGreen +HotKhaki +HotMagenta +HotOliveGreen +HotOrange +HotOrchid +HotRed +HotSalmon +HotSeaGreen +HotSlateBlue +HotSlateGray +HotTurquoise +HotViolet +*/ +``` + +生成 **Stream** 后,所有的基本功能方法,甚至更多就都可以使用了。 -## 选择Map的部分 +## 选择Map片段 +由 **TreeMap** 和 **ConcurrentSkipListMap** 实现的 **NavigableMap** 接口解决了需要选择Map片段的问题。下面是一个示例,使用了 **HTMLColors** : + +```java +// collectiontopics/NavMap.java +// NavigableMap produces pieces of a Map +import java.util.*; +import java.util.concurrent.*; +import static onjava.HTMLColors.*; + +public class NavMap { + public static final + NavigableMap COLORS = + new ConcurrentSkipListMap<>(MAP); + public static void main(String[] args) { + show(COLORS.firstEntry()); + border(); + show(COLORS.lastEntry()); + border(); + NavigableMap toLime = + COLORS.headMap(rgb("Lime"), true); + show(toLime); + border(); + show(COLORS.ceilingEntry(rgb("DeepSkyBlue") - 1)); + border(); + show(COLORS.floorEntry(rgb("DeepSkyBlue") - 1)); + border(); + show(toLime.descendingMap()); + border(); + show(COLORS.tailMap(rgb("MistyRose"), true)); + border(); + show(COLORS.subMap( + rgb("Orchid"), true, + rgb("DarkSalmon"), false)); + } +} +/* Output: +0x000000: Black +****************************** +0xFFFFFF: White +****************************** +0x000000: Black +0x000080: Navy +0x00008B: DarkBlue +0x0000CD: MediumBlue +0x0000FF: Blue +0x006400: DarkGreen +0x008000: Green +0x008080: Teal +0x008B8B: DarkCyan +0x00BFFF: DeepSkyBlue +0x00CED1: DarkTurquoise +0x00FA9A: MediumSpringGreen +0x00FF00: Lime +****************************** +0x00BFFF: DeepSkyBlue +****************************** +0x008B8B: DarkCyan +****************************** +0x00FF00: Lime +0x00FA9A: MediumSpringGreen +0x00CED1: DarkTurquoise +0x00BFFF: DeepSkyBlue +0x008B8B: DarkCyan +0x008080: Teal +0x008000: Green +0x006400: DarkGreen +0x0000FF: Blue +0x0000CD: MediumBlue +0x00008B: DarkBlue +0x000080: Navy +0x000000: Black +****************************** +0xFFE4E1: MistyRose +0xFFEBCD: BlanchedAlmond +0xFFEFD5: PapayaWhip +0xFFF0F5: LavenderBlush +0xFFF5EE: SeaShell +0xFFF8DC: Cornsilk +0xFFFACD: LemonChiffon +0xFFFAF0: FloralWhite +0xFFFAFA: Snow +0xFFFF00: Yellow +0xFFFFE0: LightYellow +0xFFFFF0: Ivory +0xFFFFFF: White +****************************** +0xDA70D6: Orchid +0xDAA520: GoldenRod +0xDB7093: PaleVioletRed +0xDC143C: Crimson +0xDCDCDC: Gainsboro +0xDDA0DD: Plum +0xDEB887: BurlyWood +0xE0FFFF: LightCyan +0xE6E6FA: Lavender +*/ +``` + +在 **main()** 方法中可以看到 **NavigableMap** 的各种功能。 因为 **NavigableMap** 具有键顺序,所以它使用了 **firstEntry()** 和 **lastEntry()** 的概念。调用 **headMap()** 会生成一个 **NavigableMap** ,其中包含了从 **Map** 的开头到 **headMap()** 参数中所指向的一组元素,其中 **boolean** 值指示结果中是否包含该参数。调用 **tailMap()** 执行了类似的操作,只不过是从参数开始到 **Map** 的末尾。 **subMap()** 则允许生成 **Map** 中间的一部分。 + +**ceilingEntry()** 从当前键值对向上搜索下一个键值对,**floorEntry()** 则是向下搜索。 **descendingMap()** 反转了 **NavigableMap** 的顺序。 + +如果需要通过分割 **Map** 来简化所正在解决的问题,则 **NavigableMap** 可以做到。具有类似的功能的其它集合实现也可以用来帮助解决问题。 -## 集合的fill方法 +## 填充集合