ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## [集合的打印](https://lingcoder.gitee.io/onjava8/#/book/12-Collections?id=%e9%9b%86%e5%90%88%e7%9a%84%e6%89%93%e5%8d%b0) 必须使用`Arrays.toString()`来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的Java集合: ~~~ // collections/PrintingCollections.java // Collections print themselves automatically import java.util.*; public class PrintingCollections { static Collection fill(Collection<String> collection) { collection.add("rat"); collection.add("cat"); collection.add("dog"); collection.add("dog"); return collection; } static Map fill(Map<String, String> map) { map.put("rat", "Fuzzy"); map.put("cat", "Rags"); map.put("dog", "Bosco"); map.put("dog", "Spot"); return map; } public static void main(String[] args) { System.out.println(fill(new ArrayList<>())); System.out.println(fill(new LinkedList<>())); System.out.println(fill(new HashSet<>())); System.out.println(fill(new TreeSet<>())); System.out.println(fill(new LinkedHashSet<>())); System.out.println(fill(new HashMap<>())); System.out.println(fill(new TreeMap<>())); System.out.println(fill(new LinkedHashMap<>())); } } /* Output: [rat, cat, dog, dog] [rat, cat, dog, dog] [rat, cat, dog] [cat, dog, rat] [rat, cat, dog] {rat=Fuzzy, cat=Rags, dog=Spot} {cat=Rags, dog=Spot, rat=Fuzzy} {rat=Fuzzy, cat=Rags, dog=Spot} */ ~~~ 这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。**Collection**类型在每个槽中只能保存一个元素。此类集合包括:**List**,它以特定的顺序保存一组元素;**Set**,其中元素不允许重复;**Queue**,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。**Map**在每个槽中存放了两个元素,即*键*和与之关联的*值*。 默认的打印行为,使用集合提供的`toString()`方法即可生成可读性很好的结果。**Collection**打印出的内容用方括号括住,每个元素由逗号分隔。**Map**则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。 第一个`fill()`方法适用于所有类型的**Collection**,这些类型都实现了`add()`方法以添加新元素。 **ArrayList**和**LinkedList**都是**List**的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且**LinkedList**包含的操作多于**ArrayList**。本章后面将对这些内容进行更全面的探讨。 **HashSet**,**TreeSet**和**LinkedHashSet**是**Set**的类型。从输出中可以看到,**Set**仅保存每个相同项中的一个,并且不同的**Set**实现存储元素的方式也不同。**HashSet**使用相当复杂的方法存储元素,这在[附录:集合主题](https://lingcoder.gitee.io/onjava8/#/)中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是**Set**的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用**TreeSet**,它将按比较结果的升序保存对象)或**LinkedHashSet**,它按照被添加的先后顺序保存对象。 **Map**(也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个**Map**将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。正是由于这种行为,对于每个键,**Map**只存储一次。 `Map.put(key, value)`添加一个所想要添加的值并将它与一个键(用来查找值)相关联。`Map.get(key)`生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。 请注意,这里没有指定(或考虑)**Map**的大小,因为它会自动调整大小。 此外,**Map**还知道如何打印自己,它会显示相关联的键和值。 本例使用了**Map**的三种基本风格:**HashMap**,**TreeMap**和**LinkedHashMap**。 键和值保存在**HashMap**中的顺序不是插入顺序,因为**HashMap**实现使用了非常快速的算法来控制顺序。**TreeMap**通过比较结果的升序来保存键,**LinkedHashMap**在保持**HashMap**查找速度的同时按键的插入顺序保存键。 ## [列表List](https://lingcoder.gitee.io/onjava8/#/book/12-Collections?id=%e5%88%97%e8%a1%a8list) **List**承诺将元素保存在特定的序列中。**List**接口在**Collection**的基础上添加了许多方法,允许在**List**的中间插入和删除元素。 有两种类型的**List**: * 基本的**ArrayList**,擅长随机访问元素,但在**List**中间插入和删除元素时速度较慢。 * **LinkedList**,它通过代价较低的在**List**中间进行的插入和删除操作,提供了优化的顺序访问。**LinkedList**对于随机访问来说相对较慢,但它具有比**ArrayList**更大的特征集。 下面的示例导入**typeinfo.pets**,超前使用了[类型信息](https://lingcoder.gitee.io/onjava8/#/)一章中的类库。这个类库包含了**Pet**类层次结构,以及用于随机生成**Pet**对象的一些工具类。此时不需要了解完整的详细信息,只需要知道两点: 1. 有一个**Pet**类,以及**Pet**的各种子类型。 2. 静态的`Pets.arrayList()`方法返回一个填充了随机选取的**Pet**对象的**ArrayList**: ~~~ // collections/ListFeatures.java import typeinfo.pets.*; import java.util.*; public class ListFeatures { public static void main(String[] args) { Random rand = new Random(47); List<Pet> pets = Pets.list(7); System.out.println("1: " + pets); Hamster h = new Hamster(); pets.add(h); // Automatically resizes System.out.println("2: " + pets); System.out.println("3: " + pets.contains(h)); pets.remove(h); // Remove by object Pet p = pets.get(2); System.out.println( "4: " + p + " " + pets.indexOf(p)); Pet cymric = new Cymric(); System.out.println("5: " + pets.indexOf(cymric)); System.out.println("6: " + pets.remove(cymric)); // Must be the exact object: System.out.println("7: " + pets.remove(p)); System.out.println("8: " + pets); pets.add(3, new Mouse()); // Insert at an index System.out.println("9: " + pets); List<Pet> sub = pets.subList(1, 4); System.out.println("subList: " + sub); System.out.println("10: " + pets.containsAll(sub)); Collections.sort(sub); // In-place sort System.out.println("sorted subList: " + sub); // Order is not important in containsAll(): System.out.println("11: " + pets.containsAll(sub)); Collections.shuffle(sub, rand); // Mix it up System.out.println("shuffled subList: " + sub); System.out.println("12: " + pets.containsAll(sub)); List<Pet> copy = new ArrayList<>(pets); sub = Arrays.asList(pets.get(1), pets.get(4)); System.out.println("sub: " + sub); copy.retainAll(sub); System.out.println("13: " + copy); copy = new ArrayList<>(pets); // Get a fresh copy copy.remove(2); // Remove by index System.out.println("14: " + copy); copy.removeAll(sub); // Only removes exact objects System.out.println("15: " + copy); copy.set(1, new Mouse()); // Replace an element System.out.println("16: " + copy); copy.addAll(2, sub); // Insert a list in the middle System.out.println("17: " + copy); System.out.println("18: " + pets.isEmpty()); pets.clear(); // Remove all elements System.out.println("19: " + pets); System.out.println("20: " + pets.isEmpty()); pets.addAll(Pets.list(4)); System.out.println("21: " + pets); Object[] o = pets.toArray(); System.out.println("22: " + o[3]); Pet[] pa = pets.toArray(new Pet[0]); System.out.println("23: " + pa[3].id()); } } /* Output: 1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug] 2: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster] 3: true 4: Cymric 2 5: -1 6: false 7: true 8: [Rat, Manx, Mutt, Pug, Cymric, Pug] 9: [Rat, Manx, Mutt, Mouse, Pug, Cymric, Pug] subList: [Manx, Mutt, Mouse] 10: true sorted subList: [Manx, Mouse, Mutt] 11: true shuffled subList: [Mouse, Manx, Mutt] 12: true sub: [Mouse, Pug] 13: [Mouse, Pug] 14: [Rat, Mouse, Mutt, Pug, Cymric, Pug] 15: [Rat, Mutt, Cymric, Pug] 16: [Rat, Mouse, Cymric, Pug] 17: [Rat, Mouse, Mouse, Pug, Cymric, Pug] 18: false 19: [] 20: true 21: [Manx, Cymric, Rat, EgyptianMau] 22: EgyptianMau 23: 14 */ ~~~ 打印行都编了号,因此可从输出追溯到源代码。 第 1 行输出展示了原始的由**Pet**组成的**List**。 与数组不同,**List**可以在创建后添加或删除元素,并自行调整大小。这正是它的重要价值:一种可修改的序列。在第 2 行输出中可以看到添加一个**Hamster**的结果,该对象将被追加到列表的末尾。 可以使用`contains()`方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给`remove()`方法。同样,如果有一个对象的引用,可以使用`indexOf()`在**List**中找到该对象所在位置的下标号,如第 4 行输出所示中所示。 当确定元素是否是属于某个**List**,寻找某个元素的索引,以及通过引用从**List**中删除元素时,都会用到`equals()`方法(根类**Object**的一个方法)。每个**Pet**被定义为一个唯一的对象,所以即使列表中已经有两个**Cymrics**,如果再创建一个新的**Cymric**对象并将其传递给`indexOf()`方法,结果仍为**\-1**(表示未找到),并且尝试调用`remove()`方法来删除这个对象将返回**false**。对于其他类,`equals()`的定义可能有所不同。例如,如果两个**String**的内容相同,则这两个**String**相等。因此,为了防止出现意外,请务必注意**List**行为会根据`equals()`行为而发生变化。 第 7、8 行输出展示了删除与**List**中的对象完全匹配的对象是成功的。 可以在**List**的中间插入一个元素,就像在第 9 行输出和它之前的代码那样。但这会带来一个问题:对于**LinkedList**,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但对于**ArrayList**,这可是代价高昂的操作。这是否意味着永远不应该在**ArrayList**的中间插入元素,并最好是转换为**LinkedList**?不,它只是意味着你应该意识到这个问题,如果你开始在某个**ArrayList**中间执行很多插入操作,并且程序开始变慢,那么你应该看看你的**List**实现有可能就是罪魁祸首(发现此类瓶颈的最佳方式是使用分析器 profiler)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到发现必须要去担心它了(尽管去理解这些问题总是一个很好的主意)。 `subList()`方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的`containsAll()`方法时,很自然地会得到**true**。请注意,顺序并不重要,在第 11、12 行输出中可以看到,在**sub**上调用直观命名的`Collections.sort()`和`Collections.shuffle()`方法,不会影响`containsAll()`的结果。`subList()`所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。 `retainAll()`方法实际上是一个“集合交集”操作,在本例中,它保留了同时在**copy**和**sub**中的所有元素。请再次注意,所产生的结果行为依赖于`equals()`方法。 第 14 行输出展示了使用索引号来删除元素的结果,与通过对象引用来删除元素相比,它显得更加直观,因为在使用索引时,不必担心`equals()`的行为。 `removeAll()`方法也是基于`equals()`方法运行的。 顾名思义,它会从**List**中删除在参数**List**中的所有元素。 `set()`方法的命名显得很不合时宜,因为它与**Set**类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。 第 17 行输出表明,对于**List**,有一个重载的`addAll()`方法可以将新列表插入到原始列表的中间位置,而不是仅能用**Collection**的`addAll()`方法将其追加到列表的末尾。 第 18 - 20 行输出展示了`isEmpty()`和`clear()`方法的效果。 第 22、23 行输出展示了如何使用`toArray()`方法将任意的**Collection**转换为数组。这是一个重载方法,其无参版本返回一个**Object**数组,但是如果将目标类型的数组传递给这个重载版本,那么它会生成一个指定类型的数组(假设它通过了类型检查)。如果参数数组太小而无法容纳**List**中的所有元素(就像本例一样),则`toArray()`会创建一个具有合适尺寸的新数组。**Pet**对象有一个`id()`方法,可以在所产生的数组中的对象上调用这个方法。