12-Collections
第十二章 集合
如果一个程序只包含固定数量的对象且对象的生命周期都是已知的,那么这是一个非常简单的程序。
通常,程序总是根据运行时才知道的某些条件去创建新的对象。在此之前,无法知道所需对象的数量甚至确切类型。为了解决这个普遍的编程问题,需要在任意时刻和任意位置创建任意数量的对象。因此,不能依靠创建命名的引用来持有每一个对象:
MyType aReference;
因为从来不会知道实际需要多少个这样的引用。
大多数编程语言都提供了某种方法来解决这个基本问题。
集合还有一些其它特性。例如,
尽管在
泛型和类型安全的集合
使用add()
插入对象;然后用 get()
来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号2。 size()
方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出运行时异常,异常章节介绍了异常
在本例中, @SuppressWarning
注解及其参数表示只抑制“unchecked”类型的警告(注解章节将介绍更多有关注解的信息
// collections/ApplesAndOrangesWithoutGenerics.java
// Simple collection use (suppressing compiler warnings)
// {ThrowsException}
import java.util.*;
class Apple {
private static long counter;
private final long id = counter++;
public long id() { return id; }
}
class Orange {}
public class ApplesAndOrangesWithoutGenerics {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
ArrayList apples = new ArrayList();
for(int i = 0; i < 3; i++)
apples.add(new Apple());
// No problem adding an Orange to apples:
apples.add(new Orange());
for(Object apple : apples) {
((Apple) apple).id();
// Orange is detected only at run time
}
}
}
/* Output:
___[ Error Output ]___
Exception in thread "main"
java.lang.ClassCastException: Orange cannot be cast to Apple
at ApplesAndOrangesWithoutGenerics.main(ApplesAndOrangesWithoutGenerics.java:23)
*/
add()
方法将get()
方法来取出你认为是id()
方法之前,强制执行转型。否则,将会产生语法错误。
在运行时,当尝试将
在泛型章节中,你将了解到使用
通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中3。下面还是这个示例,但是使用了泛型:
// collections/ApplesAndOrangesWithGenerics.java
import java.util.*;
public class ApplesAndOrangesWithGenerics {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<>();
for(int i = 0; i < 3; i++)
apples.add(new Apple());
// Compile-time error:
// apples.add(new Orange());
for(Apple apple : apples) {
System.out.println(apple.id());
}
}
}
/* Output:
0
1
2
*/
在new ArrayList<>()
。这有时被称为“菱形语法”(diamond syntax
ArrayList<Apple> apples = new ArrayList<Apple>();
随着类型变得越来越复杂,这种重复产生的代码非常混乱且难以阅读。程序员发现所有类型信息都可以从左侧获得,因此,编译器没有理由强迫右侧再重复这些。虽然类型推断(type inference)只是个很小的请求,
有了
使用泛型,从get()
时,它会替你执行转型。因此,使用泛型,你不仅知道编译器将检查放入集合的对象类型,而且在使用集合中的对象时也可以获得更清晰的语法。
当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型:
// collections/GenericsAndUpcasting.java
import java.util.*;
class GrannySmith extends Apple {}
class Gala extends Apple {}
class Fuji extends Apple {}
class Braeburn extends Apple {}
public class GenericsAndUpcasting {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<>();
apples.add(new GrannySmith());
apples.add(new Gala());
apples.add(new Fuji());
apples.add(new Braeburn());
for(Apple apple : apples)
System.out.println(apple);
}
}
/* Output:
GrannySmith@15db9742
Gala@6d06d69c
Fuji@7852e922
Braeburn@4e25154f
*/
因此,可以将
程序的输出是从toString()
方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 hashCode()
方法产生的
基本概念
- 集合(Collection) :一个独立元素的序列,这些元素都服从一条或多条规则。
List 必须以插入的顺序保存元素, Set 不能包含重复元素, Queue 按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同 ) 。 - 映射(Map) : 一组成对的“键值对”对象,允许使用键来查找值。
ArrayList 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 map 允许我们使用一个对象来查找另一个对象,它也被称作关联数组(associative array ) ,因为它将对象和其它对象关联在一起;或者称作字典(dictionary) ,因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。Map 是强大的编程工具。
尽管并非总是可行,但在理想情况下,你编写的大部分代码都在与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,可以像下面这样创建一个
List<Apple> apples = new ArrayList<>();
请注意,
List<Apple> apples = new LinkedList<>();
因此,应该创建一个具体类的对象,将其向上转型为对应的接口,然后在其余代码中都是用这个接口。
这种方式并非总是有效的,因为某些具体类有额外的功能。例如,
// collections/SimpleCollection.java
import java.util.*;
public class SimpleCollection {
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<>();
for(int i = 0; i < 10; i++)
c.add(i); // Autoboxing
for(Integer i : c)
System.out.print(i + ", ");
}
}
/* Output:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
*/
这个例子仅使用了add()
add()
方法的名称就表明它是在add()
“要确保这个add()
总是表示“把它放进去”,因为
可以使用
添加元素组
在
Arrays.asList()
方法接受一个数组或是逗号分隔的元素列表(使用可变参数Collections.addAll()
方法接受一个addAll()
方法:
// collections/AddingGroups.java
// Adding groups of elements to Collection objects
import java.util.*;
public class AddingGroups {
public static void main(String[] args) {
Collection<Integer> collection =
new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Integer[] moreInts = { 6, 7, 8, 9, 10 };
collection.addAll(Arrays.asList(moreInts));
// Runs significantly faster, but you can't
// construct a Collection this way:
Collections.addAll(collection, 11, 12, 13, 14, 15);
Collections.addAll(collection, moreInts);
// Produces a list "backed by" an array:
List<Integer> list = Arrays.asList(16,17,18,19,20);
list.set(1, 99); // OK -- modify an element
// list.add(21); // Runtime error; the underlying
// array cannot be resized.
}
}
Arrays.asList()
来为这个构造器产生输入。但是, Collections.addAll()
运行得更快,而且很容易构建一个不包含元素的Collections.addAll()
,因此这是首选方式。
Collection.addAll()
方法只能接受另一个Arrays.asList()
或 Collections.addAll()
灵活。这两个方法都使用可变参数列表。
也可以直接使用 Arrays.asList()
的输出作为一个add()
或 remove()
,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:
// collections/AsListInference.java
import java.util.*;
class Snow {}
class Powder extends Snow {}
class Light extends Powder {}
class Heavy extends Powder {}
class Crusty extends Snow {}
class Slush extends Snow {}
public class AsListInference {
public static void main(String[] args) {
List<Snow> snow1 = Arrays.asList(
new Crusty(), new Slush(), new Powder());
//- snow1.add(new Heavy()); // Exception
List<Snow> snow2 = Arrays.asList(
new Light(), new Heavy());
//- snow2.add(new Slush()); // Exception
List<Snow> snow3 = new ArrayList<>();
Collections.addAll(snow3,
new Light(), new Heavy(), new Powder());
snow3.add(new Crusty());
// Hint with explicit type argument specification:
List<Snow> snow4 = Arrays.<Snow>asList(
new Light(), new Heavy(), new Slush());
//- snow4.add(new Powder()); // Exception
}
}
在Arrays.asList()
中间的“暗示”(即 <Snow>
Arrays.asList()
生成的结果
集合的打印
必须使用 Arrays.toString()
来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的
// 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}
*/
这显示了
默认的打印行为,使用集合提供的 toString()
方法即可生成可读性很好的结果。
第一个 fill()
方法适用于所有类型的add()
方法以添加新元素。
HashSet ,
Map (也称为关联数组)使用键来查找对象,就像一个简单的数据库。所关联的对象称为值。 假设有一个
Map.put(key, value)
添加一个所想要添加的值并将它与一个键(用来查找值)相关联。 Map.get(key)
生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。
请注意,这里没有指定(或考虑)
本例使用了
键和值保存在
列表List
有两种类型的
- 基本的
ArrayList ,擅长随机访问元素,但在List 中间插入和删除元素时速度较慢。 - LinkedList ,它通过代价较低的在
List 中间进行的插入和删除操作,提供了优化的顺序访问。 LinkedList 对于随机访问来说相对较慢,但它具有比 ArrayList 更大的特征集。
下面的示例导入
- 有一个
Pet 类,以及 Pet 的各种子类型。 - 静态的
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
*/
打印行都编了号,因此可从输出追溯到源代码。 第
可以使用 contains()
方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给 remove()
方法。同样,如果有一个对象的引用,可以使用 indexOf()
在
当确定元素是否是属于某个equals()
方法(根类indexOf()
方法,结果仍为remove()
方法来删除这个对象将返回equals()
的定义可能有所不同。例如,如果两个equals()
行为而发生变化。
第
可以在
subList()
方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的 containsAll()
方法时,很自然地会得到Collections.sort()
和 Collections.shuffle()
方法,不会影响 containsAll()
的结果。 subList()
所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。
retainAll()
方法实际上是一个“集合交集”操作,在本例中,它保留了同时在equals()
方法。
第equals()
的行为。
removeAll()
方法也是基于 equals()
方法运行的。 顾名思义,它会从
set()
方法的命名显得很不合时宜,因为它与
第addAll()
方法可以将新列表插入到原始列表的中间位置,而不是仅能用addAll()
方法将其追加到列表的末尾。
第isEmpty()
和 clear()
方法的效果。
第toArray()
方法将任意的toArray()
会创建一个具有合适尺寸的新数组。 id()
方法,可以在所产生的数组中的对象上调用这个方法。
迭代器Iterators
在任何集合中,都必须有某种方式可以插入元素并再次获取它们。毕竟,保存事物是集合最基本的工作。对于add()
是插入元素的一种方式, get()
是获取元素的一种方式。
如果从更高层次的角度考虑,会发现这里有个缺点:要使用集合,必须对集合的确切类型编程。这一开始可能看起来不是很糟糕,但是考虑下面的情况:如果原本是对
迭代器(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为轻量级对象(lightweight object
- 使用
iterator()
方法要求集合返回一个Iterator 。Iterator 将准备好返回序列中的第一个元素。 - 使用
next()
方法获得序列中的下一个元素。 - 使用
hasNext()
方法检查序列中是否还有元素。 - 使用
remove()
方法将迭代器最近返回的那个元素删除。
为了观察它的工作方式,这里再次使用类型信息章节中的
// collections/SimpleIteration.java
import typeinfo.pets.*;
import java.util.*;
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.list(12);
Iterator<Pet> it = pets.iterator();
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
// A simpler approach, when possible:
for(Pet p : pets)
System.out.print(p.id() + ":" + p + " ");
System.out.println();
// An Iterator can also remove elements:
it = pets.iterator();
for(int i = 0; i < 6; i++) {
it.next();
it.remove();
}
System.out.println(pets);
}
}
/* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug
7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug
7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]
*/
有了hasNext()
和 next()
关心的事情。
如果只是想向前遍历
next()
生成的最后一个元素,这意味着在调用 remove()
之前必须先调用 next()
。4
在集合中的每个对象上执行操作,这种思想十分强大,并且贯穿于本书。
现在考虑创建一个 display()
方法,它不必知晓集合的确切类型:
// collections/CrossCollectionIteration.java
import typeinfo.pets.*;
import java.util.*;
public class CrossCollectionIteration {
public static void display(Iterator<Pet> it) {
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Pet> pets = Pets.list(8);
LinkedList<Pet> petsLL = new LinkedList<>(pets);
HashSet<Pet> petsHS = new HashSet<>(pets);
TreeSet<Pet> petsTS = new TreeSet<>(pets);
display(pets.iterator());
display(petsLL.iterator());
display(petsHS.iterator());
display(petsTS.iterator());
}
}
/* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat
*/
display()
方法不包含任何有关它所遍历的序列的类型信息。这也展示了
我们可以使用
// collections/CrossCollectionIteration2.java
import typeinfo.pets.*;
import java.util.*;
public class CrossCollectionIteration2 {
public static void display(Iterable<Pet> ip) {
Iterator<Pet> it = ip.iterator();
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Pet> pets = Pets.list(8);
LinkedList<Pet> petsLL = new LinkedList<>(pets);
HashSet<Pet> petsHS = new HashSet<>(pets);
TreeSet<Pet> petsTS = new TreeSet<>(pets);
display(pets);
display(petsLL);
display(petsHS);
display(petsTS);
}
}
/* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat
*/
这里所有的类都是display()
的调用显然更简单。
ListIterator
set()
方法替换它访问过的最近一个元素。可以通过调用 listIterator()
方法来生成指向listIterator(n)
创建一个一开始就指向列表索引号为
// collections/ListIteration.java
import typeinfo.pets.*;
import java.util.*;
public class ListIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.list(8);
ListIterator<Pet> it = pets.listIterator();
while(it.hasNext())
System.out.print(it.next() +
", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
System.out.println();
// Backwards:
while(it.hasPrevious())
System.out.print(it.previous().id() + " ");
System.out.println();
System.out.println(pets);
it = pets.listIterator(3);
while(it.hasNext()) {
it.next();
it.set(Pets.get());
}
System.out.println(pets);
}
}
/* Output:
Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;
7 6 5 4 3 2 1 0
[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]
[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]
*/
Pets.get()
方法用来从位置
链表LinkedList
getFirst()
和element()
是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果List 为空,则抛出 NoSuchElementException 异常。 peek()
方法与这两个方法只是稍有差异,它在列表为空时返回null 。removeFirst()
和remove()
也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出NoSuchElementException 异常。 poll()
稍有差异,它在列表为空时返回null 。addFirst()
在列表的开头插入一个元素。offer()
与add()
和addLast()
相同。 它们都在列表的尾部(末尾)添加一个元素。removeLast()
删除并返回列表的最后一个元素。
下面的示例展示了这些功能之间基本的相似性和差异性。它并不是重复执行
// collections/LinkedListFeatures.java
import typeinfo.pets.*;
import java.util.*;
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<Pet> pets =
new LinkedList<>(Pets.list(5));
System.out.println(pets);
// Identical:
System.out.println(
"pets.getFirst(): " + pets.getFirst());
System.out.println(
"pets.element(): " + pets.element());
// Only differs in empty-list behavior:
System.out.println("pets.peek(): " + pets.peek());
// Identical; remove and return the first element:
System.out.println(
"pets.remove(): " + pets.remove());
System.out.println(
"pets.removeFirst(): " + pets.removeFirst());
// Only differs in empty-list behavior:
System.out.println("pets.poll(): " + pets.poll());
System.out.println(pets);
pets.addFirst(new Rat());
System.out.println("After addFirst(): " + pets);
pets.offer(Pets.get());
System.out.println("After offer(): " + pets);
pets.add(Pets.get());
System.out.println("After add(): " + pets);
pets.addLast(new Hamster());
System.out.println("After addLast(): " + pets);
System.out.println(
"pets.removeLast(): " + pets.removeLast());
}
}
/* Output:
[Rat, Manx, Cymric, Mutt, Pug]
pets.getFirst(): Rat
pets.element(): Rat
pets.peek(): Rat
pets.remove(): Rat
pets.removeFirst(): Manx
pets.poll(): Cymric
[Mutt, Pug]
After addFirst(): [Rat, Mutt, Pug]
After offer(): [Rat, Mutt, Pug, Cymric]
After add(): [Rat, Mutt, Pug, Cymric, Pug]
After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]
pets.removeLast(): Hamster
*/
Pets.list()
的结果被传递给element()
, offer()
, peek()
, poll()
和 remove()
方法,以使其可以成为一个
堆栈Stack
堆栈是“后进先出”(LIFO)集合。它有时被称为叠加栈(pushdown stack
// collections/StackTest.java
import java.util.*;
public class StackTest {
public static void main(String[] args) {
Deque<String> stack = new ArrayDeque<>();
for(String s : "My dog has fleas".split(" "))
stack.push(s);
while(!stack.isEmpty())
System.out.print(stack.pop() + " ");
}
}
/* Output:
fleas has dog My
*/
即使它是作为一个堆栈在使用,我们仍然必须将其声明为
// onjava/Stack.java
// A Stack class built with an ArrayDeque
package onjava;
import java.util.Deque;
import java.util.ArrayDeque;
public class Stack<T> {
private Deque<T> storage = new ArrayDeque<>();
public void push(T v) { storage.push(v); }
public T peek() { return storage.peek(); }
public T pop() { return storage.pop(); }
public boolean isEmpty() { return storage.isEmpty(); }
@Override
public String toString() {
return storage.toString();
}
}
这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的push()
接受类型为peek()
和 pop()
返回类型为peek()
方法将返回栈顶元素,但并不将其从栈顶删除,而 pop()
删除并返回顶部元素。
如果只需要栈的行为,那么使用继承是不合适的,因为这将产生一个具有
下面将使用
// collections/StackTest2.java
import onjava.*;
public class StackTest2 {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for(String s : "My dog has fleas".split(" "))
stack.push(s);
while(!stack.isEmpty())
System.out.print(stack.pop() + " ");
}
}
/* Output:
fleas has dog My
*/
如果想在自己的代码中使用这个
// collections/StackCollision.java
public class StackCollision {
public static void main(String[] args) {
onjava.Stack<String> stack = new onjava.Stack<>();
for(String s : "My dog has fleas".split(" "))
stack.push(s);
while(!stack.isEmpty())
System.out.print(stack.pop() + " ");
System.out.println();
java.util.Stack<String> stack2 =
new java.util.Stack<>();
for(String s : "My dog has fleas".split(" "))
stack2.push(s);
while(!stack2.empty())
System.out.print(stack2.pop() + " ");
}
}
/* Output:
fleas has dog My
fleas has dog My
*/
尽管已经有了
还可以使用显式导入来控制对“首选”
import onjava.Stack;
现在
集合Set
下面是使用存放
// collections/SetOfInteger.java
import java.util.*;
public class SetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> intset = new HashSet<>();
for(int i = 0; i < 10000; i++)
intset.add(rand.nextInt(30));
System.out.println(intset);
}
}
/* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
*/
在
早期
// collections/SetOfString.java
import java.util.*;
public class SetOfString {
public static void main(String[] args) {
Set<String> colors = new HashSet<>();
for(int i = 0; i < 100; i++) {
colors.add("Yellow");
colors.add("Blue");
colors.add("Red");
colors.add("Red");
colors.add("Orange");
colors.add("Yellow");
colors.add("Blue");
colors.add("Purple");
}
System.out.println(colors);
}
}
/* Output:
[Red, Yellow, Blue, Purple, Orange]
*/
// collections/SortedSetOfString.java
import java.util.*;
public class SortedSetOfString {
public static void main(String[] args) {
Set<String> colors = new TreeSet<>();
for(int i = 0; i < 100; i++) {
colors.add("Yellow");
colors.add("Blue");
colors.add("Red");
colors.add("Red");
colors.add("Orange");
colors.add("Yellow");
colors.add("Blue");
colors.add("Purple");
}
System.out.println(colors);
}
}
/* Output:
[Blue, Orange, Purple, Red, Yellow]
*/
最常见的操作之一是使用 contains()
测试成员归属性,但也有一些其它操作,这可能会让你想起在小学学过的维恩图(译者注:利用图形的交合表示多个集合之间的逻辑关系
// collections/SetOperations.java
import java.util.*;
public class SetOperations {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
Collections.addAll(set1,
"A B C D E F G H I J K L".split(" "));
set1.add("M");
System.out.println("H: " + set1.contains("H"));
System.out.println("N: " + set1.contains("N"));
Set<String> set2 = new HashSet<>();
Collections.addAll(set2, "H I J K L".split(" "));
System.out.println(
"set2 in set1: " + set1.containsAll(set2));
set1.remove("H");
System.out.println("set1: " + set1);
System.out.println(
"set2 in set1: " + set1.containsAll(set2));
set1.removeAll(set2);
System.out.println(
"set2 removed from set1: " + set1);
Collections.addAll(set1, "X Y Z".split(" "));
System.out.println(
"'X Y Z' added to set1: " + set1);
}
}
/* Output:
H: true
N: false
set2 in set1: true
set1: [A, B, C, D, E, F, G, I, J, K, L, M]
set2 in set1: false
set2 removed from set1: [A, B, C, D, E, F, G, M]
'X Y Z' added to set1: [A, B, C, D, E, F, G, M, X, Y, Z]
*/
这些方法名都是自解释的,
能够产生每个元素都唯一的列表是相当有用的功能。例如,假设想要列出上面的java.nio.file.Files.readAllLines()
方法,可以打开一个文件,并将其作为一个
// collections/UniqueWords.java
import java.util.*;
import java.nio.file.*;
public class UniqueWords {
public static void
main(String[] args) throws Exception {
List<String> lines = Files.readAllLines(
Paths.get("SetOperations.java"));
Set<String> words = new TreeSet<>();
for(String line : lines)
for(String word : line.split("\\W+"))
if(word.trim().length() > 0)
words.add(word);
System.out.println(words);
}
}
/* Output:
[A, B, C, Collections, D, E, F, G, H, HashSet, I, J, K, L, M, N, Output, Set, SetOperations, String, System, X, Y, Z, add, addAll, added, args, class, collections, contains, containsAll, false, from, import, in, java, main, new, out, println, public, remove, removeAll, removed, set1, set2, split, static, to, true, util, void]
*/
我们逐步浏览文件中的每一行,并使用 String.split()
将其分解为单词,这里使用正则表达式
// collections/UniqueWordsAlphabetic.java
// Producing an alphabetic listing
import java.util.*;
import java.nio.file.*;
public class UniqueWordsAlphabetic {
public static void
main(String[] args) throws Exception {
List<String> lines = Files.readAllLines(
Paths.get("SetOperations.java"));
Set<String> words =
new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
for(String line : lines)
for(String word : line.split("\\W+"))
if(word.trim().length() > 0)
words.add(word);
System.out.println(words);
}
}
/* Output:
[A, add, addAll, added, args, B, C, class, collections, contains, containsAll, D, E, F, false, from, G, H, HashSet, I, import, in, J, java, K, L, M, main, N, new, out, Output, println, public, remove, removeAll, removed, Set, set1, set2, SetOperations, split, static, String, System, to, true, util, void, X, Y, Z]
*/
映射Map
将对象映射到其他对象的能力是解决编程问题的有效方法。例如,考虑一个程序,它被用来检查
// collections/Statistics.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Simple demonstration of HashMap
import java.util.*;
public class Statistics {
public static void main(String[] args) {
Random rand = new Random(47);
Map<Integer, Integer> m = new HashMap<>();
for(int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
int r = rand.nextInt(20);
Integer freq = m.get(r); // [1]
m.put(r, freq == null ? 1 : freq + 1);
}
System.out.println(m);
}
}
/* Output:
{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519, 7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506, 14=477, 15=497, 16=533, 17=509, 18=478, 19=464}
*/
[1] 自动包装机制将随机生成的 int 转换为可以与 HashMap 一起使用的 Integer 引用(不能使用基本类型的集合 ) 。如果键不在集合中,则get()
返回null (这意味着该数字第一次出现) 。否则,get()
会为键生成与之关联的Integer 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 Integer 的装箱和拆箱 ) 。
接下来的示例将使用一个containsKey()
和 containsValue()
方法去测试一个
// collections/PetMap.java
import typeinfo.pets.*;
import java.util.*;
public class PetMap {
public static void main(String[] args) {
Map<String, Pet> petMap = new HashMap<>();
petMap.put("My Cat", new Cat("Molly"));
petMap.put("My Dog", new Dog("Ginger"));
petMap.put("My Hamster", new Hamster("Bosco"));
System.out.println(petMap);
Pet dog = petMap.get("My Dog");
System.out.println(dog);
System.out.println(petMap.containsKey("My Dog"));
System.out.println(petMap.containsValue(dog));
}
}
/* Output:
{My Dog=Dog Ginger, My Cat=Cat Molly, My
Hamster=Hamster Bosco}
Dog Ginger
true
true
*/
// collections/MapOfList.java
// {java collections.MapOfList}
package collections;
import typeinfo.pets.*;
import java.util.*;
public class MapOfList {
public static final Map<Person, List< ? extends Pet>>
petPeople = new HashMap<>();
static {
petPeople.put(new Person("Dawn"),
Arrays.asList(
new Cymric("Molly"),
new Mutt("Spot")));
petPeople.put(new Person("Kate"),
Arrays.asList(new Cat("Shackleton"),
new Cat("Elsie May"), new Dog("Margrett")));
petPeople.put(new Person("Marilyn"),
Arrays.asList(
new Pug("Louie aka Louis Snorkelstein Dupree"),
new Cat("Stanford"),
new Cat("Pinkola")));
petPeople.put(new Person("Luke"),
Arrays.asList(
new Rat("Fuzzy"), new Rat("Fizzy")));
petPeople.put(new Person("Isaac"),
Arrays.asList(new Rat("Freckly")));
}
public static void main(String[] args) {
System.out.println("People: " + petPeople.keySet());
System.out.println("Pets: " + petPeople.values());
for(Person person : petPeople.keySet()) {
System.out.println(person + " has:");
for(Pet pet : petPeople.get(person))
System.out.println(" " + pet);
}
}
}
/* Output:
People: [Person Dawn, Person Kate, Person Isaac, Person
Marilyn, Person Luke]
Pets: [[Cymric Molly, Mutt Spot], [Cat Shackleton, Cat
Elsie May, Dog Margrett], [Rat Freckly], [Pug Louie aka
Louis Snorkelstein Dupree, Cat Stanford, Cat Pinkola],
[Rat Fuzzy, Rat Fizzy]]
Person Dawn has:
Cymric Molly
Mutt Spot
Person Kate has:
Cat Shackleton
Cat Elsie May
Dog Margrett
Person Isaac has:
Rat Freckly
Person Marilyn has:
Pug Louie aka Louis Snorkelstein Dupree
Cat Stanford
Cat Pinkola
Person Luke has:
Rat Fuzzy
Rat Fizzy
*/
keySet()
方法生成由在
队列Queue
队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中尤为重要,因为它们可以安全地将对象从一个任务传输到另一个任务。
// collections/QueueDemo.java
// Upcasting to a Queue from a LinkedList
import java.util.*;
public class QueueDemo {
public static void printQ(Queue queue) {
while(queue.peek() != null)
System.out.print(queue.remove() + " ");
System.out.println();
}
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
Random rand = new Random(47);
for(int i = 0; i < 10; i++)
queue.offer(rand.nextInt(i + 10));
printQ(queue);
Queue<Character> qc = new LinkedList<>();
for(char c : "Brontosaurus".toCharArray())
qc.offer(c);
printQ(qc);
}
}
/* Output:
8 1 1 1 5 14 3 1 0 1
B r o n t o s a u r u s
*/
offer()
是peek()
和 element()
都返回队头元素而不删除它,但如果队列为空,则 peek()
返回element()
抛出poll()
和 remove()
都删除并返回队头元素,但如果队列为空,则 poll()
返回remove()
抛出
自动包装机制会自动将 nextInt()
的
优先级队列PriorityQueue
先进先出(FIFO)描述了最典型的队列规则(queuing discipline
优先级队列 :下一个弹出的元素是最需要的元素(具有最高的优先级
当在offer()
方法来插入一个对象时,该对象会在队列中被排序。5默认的排序使用队列中对象的自然顺序(natural orderpeek()
, poll()
或 remove()
方法时,获得的元素将是队列中优先级最高的元素。
让
// collections/PriorityQueueDemo.java
import java.util.*;
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue =
new PriorityQueue<>();
Random rand = new Random(47);
for(int i = 0; i < 10; i++)
priorityQueue.offer(rand.nextInt(i + 10));
QueueDemo.printQ(priorityQueue);
List<Integer> ints = Arrays.asList(25, 22, 20,
18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);
priorityQueue = new PriorityQueue<>(ints);
QueueDemo.printQ(priorityQueue);
priorityQueue = new PriorityQueue<>(
ints.size(), Collections.reverseOrder());
priorityQueue.addAll(ints);
QueueDemo.printQ(priorityQueue);
String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
List<String> strings =
Arrays.asList(fact.split(""));
PriorityQueue<String> stringPQ =
new PriorityQueue<>(strings);
QueueDemo.printQ(stringPQ);
stringPQ = new PriorityQueue<>(
strings.size(), Collections.reverseOrder());
stringPQ.addAll(strings);
QueueDemo.printQ(stringPQ);
Set<Character> charSet = new HashSet<>();
for(char c : fact.toCharArray())
charSet.add(c); // Autoboxing
PriorityQueue<Character> characterPQ =
new PriorityQueue<>(charSet);
QueueDemo.printQ(characterPQ);
}
}
/* Output:
0 1 1 1 1 1 3 5 8 14
1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25
25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1
A A B C C C D D E E E F H H I I L N N O O O O S S
S T T U U U W
W U U U T T S S S O O O O N N L I I H H F E E E D D C C
C B A A
A B C D E F H I L N O S T U W
*/
Collections.reverseOrder()
(
最后一部分添加了一个
Integer ,
集合与迭代器
使用接口的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。6因此,如果所编写的方法接受一个iterator()
方法:
// collections/InterfaceVsIterator.java
import typeinfo.pets.*;
import java.util.*;
public class InterfaceVsIterator {
public static void display(Iterator<Pet> it) {
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void display(Collection<Pet> pets) {
for(Pet p : pets)
System.out.print(p.id() + ":" + p + " ");
System.out.println();
}
public static void main(String[] args) {
List<Pet> petList = Pets.list(8);
Set<Pet> petSet = new HashSet<>(petList);
Map<String, Pet> petMap = new LinkedHashMap<>();
String[] names = ("Ralph, Eric, Robin, Lacey, " +
"Britney, Sam, Spot, Fluffy").split(", ");
for(int i = 0; i < names.length; i++)
petMap.put(names[i], petList.get(i));
display(petList);
display(petSet);
display(petList.iterator());
display(petSet.iterator());
System.out.println(petMap);
System.out.println(petMap.keySet());
display(petMap.values());
display(petMap.values().iterator());
}
}
/* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
{Ralph=Rat, Eric=Manx, Robin=Cymric, Lacey=Mutt, Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx}
[Ralph, Eric, Robin, Lacey, Britney, Sam, Spot, Fluffy]
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
*/
两个版本的 display()
方法都可以使用display()
方法与低层集合的特定实现解耦。
在本例中,这两种方式都可以奏效。事实上, display(Collection)
的实现中可以使用
当需要实现一个不是display()
方法中使用它们,也要这样做。尽管通过继承iterator()
和 size()
没有实现(抽象方法
// collections/CollectionSequence.java
import typeinfo.pets.*;
import java.util.*;
public class CollectionSequence
extends AbstractCollection<Pet> {
private Pet[] pets = Pets.array(8);
@Override
public int size() { return pets.length; }
@Override
public Iterator<Pet> iterator() {
return new Iterator<Pet>() { // [1]
private int index = 0;
@Override
public boolean hasNext() {
return index < pets.length;
}
@Override
public Pet next() { return pets[index++]; }
@Override
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
CollectionSequence c = new CollectionSequence();
InterfaceVsIterator.display(c);
InterfaceVsIterator.display(c.iterator());
}
}
/* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
*/
remove()
方法是一个“可选操作”,在附录:集合主题中详细介绍。 这里可以不必实现它,如果你调用它,它将抛出异常。
[1] 你可能会认为,因为 iterator()
返回Iterator<Pet> ,匿名内部类定义可以使用菱形语法,Java 可以推断出类型。但这不起作用,类型推断仍然非常有限。
这个例子表明,如果实现了iterator()
,而单独只实现 iterator()
和继承iterator()
)要容易得多:
// collections/NonCollectionSequence.java
import typeinfo.pets.*;
import java.util.*;
class PetSequence {
protected Pet[] pets = Pets.array(8);
}
public class NonCollectionSequence extends PetSequence {
public Iterator<Pet> iterator() {
return new Iterator<Pet>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < pets.length;
}
@Override
public Pet next() { return pets[index++]; }
@Override
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
NonCollectionSequence nc =
new NonCollectionSequence();
InterfaceVsIterator.display(nc.iterator());
}
}
/* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
*/
生成
for-in 和迭代器
到目前为止,
// collections/ForInCollections.java
// All collections work with for-in
import java.util.*;
public class ForInCollections {
public static void main(String[] args) {
Collection<String> cs = new LinkedList<>();
Collections.addAll(cs,
"Take the long way home".split(" "));
for(String s : cs)
System.out.print("'" + s + "' ");
}
}
/* Output:
'Take' 'the' 'long' 'way' 'home'
*/
由于
这样做的原因是iterator()
方法。
// collections/IterableClass.java
// Anything Iterable works with for-in
import java.util.*;
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how " +
"we know the Earth to be banana-shaped."
).split(" ");
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < words.length;
}
@Override
public String next() { return words[index++]; }
@Override
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for(String s : new IterableClass())
System.out.print(s + " ");
}
}
/* Output:
And that is how we know the Earth to be banana-shaped.
*/
iterator()
返回的是实现了
在
// collections/EnvironmentVariables.java
// {VisuallyInspectOutput}
import java.util.*;
public class EnvironmentVariables {
public static void main(String[] args) {
for(Map.Entry entry: System.getenv().entrySet()) {
System.out.println(entry.getKey() + ": " +
entry.getValue());
}
}
}
System.getenv()
7返回一个entrySet()
产生一个由
// collections/ArrayIsNotIterable.java
import java.util.*;
public class ArrayIsNotIterable {
static <T> void test(Iterable<T> ib) {
for(T t : ib)
System.out.print(t + " ");
}
public static void main(String[] args) {
test(Arrays.asList(1, 2, 3));
String[] strings = { "A", "B", "C" };
// An array works in for-in, but it's not Iterable:
//- test(strings);
// You must explicitly convert it to an Iterable:
test(Arrays.asList(strings));
}
}
/* Output:
1 2 3 A B C
*/
尝试将数组作为一个
适配器方法惯用法
如果现在有一个iterator()
方法,则只能替换现有的方法,而不能实现遍历顺序的选择。
一种解决方案是所谓适配器方法(Adapter Method)的惯用法
// collections/AdapterMethodIdiom.java
// The "Adapter Method" idiom uses for-in
// with additional kinds of Iterables
import java.util.*;
class ReversibleArrayList<T> extends ArrayList<T> {
ReversibleArrayList(Collection<T> c) {
super(c);
}
public Iterable<T> reversed() {
return new Iterable<T>() {
public Iterator<T> iterator() {
return new Iterator<T>() {
int current = size() - 1;
public boolean hasNext() {
return current > -1;
}
public T next() { return get(current--); }
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class AdapterMethodIdiom {
public static void main(String[] args) {
ReversibleArrayList<String> ral =
new ReversibleArrayList<String>(
Arrays.asList("To be or not to be".split(" ")));
// Grabs the ordinary iterator via iterator():
for(String s : ral)
System.out.print(s + " ");
System.out.println();
// Hand it the Iterable of your choice
for(String s : ral.reversed())
System.out.print(s + " ");
}
}
/* Output:
To be or not to be
be to not or be To
*/
在主方法中,如果直接将reversed()
方法,它会产生不同的行为。
通过使用这种方式,可以在
// collections/MultiIterableClass.java
// Adding several Adapter Methods
import java.util.*;
public class MultiIterableClass extends IterableClass {
public Iterable<String> reversed() {
return new Iterable<String>() {
public Iterator<String> iterator() {
return new Iterator<String>() {
int current = words.length - 1;
public boolean hasNext() {
return current > -1;
}
public String next() {
return words[current--];
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
public Iterable<String> randomized() {
return new Iterable<String>() {
public Iterator<String> iterator() {
List<String> shuffled =
new ArrayList<String>(Arrays.asList(words));
Collections.shuffle(shuffled, new Random(47));
return shuffled.iterator();
}
};
}
public static void main(String[] args) {
MultiIterableClass mic = new MultiIterableClass();
for(String s : mic.reversed())
System.out.print(s + " ");
System.out.println();
for(String s : mic.randomized())
System.out.print(s + " ");
System.out.println();
for(String s : mic)
System.out.print(s + " ");
}
}
/* Output:
banana-shaped. be to Earth the know we how is that And
is banana-shaped. Earth that how the be And we know to
And that is how we know the Earth to be banana-shaped.
*/
注意,第二个方法 random()
没有创建它自己的
从输出中可以看到, Collections.shuffle()
方法不会影响到原始数组,而只是打乱了randomized()
方法用一个Arrays.asList()
的结果包装了起来。如果这个由 Arrays.asList()
生成的
// collections/ModifyingArraysAsList.java
import java.util.*;
public class ModifyingArraysAsList {
public static void main(String[] args) {
Random rand = new Random(47);
Integer[] ia = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<Integer> list1 =
new ArrayList<>(Arrays.asList(ia));
System.out.println("Before shuffling: " + list1);
Collections.shuffle(list1, rand);
System.out.println("After shuffling: " + list1);
System.out.println("array: " + Arrays.toString(ia));
List<Integer> list2 = Arrays.asList(ia);
System.out.println("Before shuffling: " + list2);
Collections.shuffle(list2, rand);
System.out.println("After shuffling: " + list2);
System.out.println("array: " + Arrays.toString(ia));
}
}
/* Output:
Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
After shuffling: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9]
array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
After shuffling: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]
array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]
*/
在第一种情况下, Arrays.asList()
的输出被传递给了Arrays.asList(ia)
的结果,这种打乱就会修改Arrays.asList()
生成一个
本章小结
-
数组将数字索引与对象相关联。它保存类型明确的对象,因此在查找对象时不必对结果做类型转换。它可以是多维的,可以保存基本类型的数据。虽然可以在运行时创建数组,但是一旦创建数组,就无法更改数组的大小。
-
Collection 保存单一的元素,而 Map 包含相关联的键值对。使用 Java 泛型,可以指定集合中保存的对象的类型,因此不能将错误类型的对象放入集合中,并且在从集合中获取元素时,不必进行类型转换。各种Collection 和各种 Map 都可以在你向其中添加更多的元素时,自动调整其尺寸大小。集合不能保存基本类型,但自动装箱机制会负责执行基本类型和集合中保存的包装类型之间的双向转换。 -
像数组一样,
List 也将数字索引与对象相关联,因此,数组和 List 都是有序集合。 -
如果要执行大量的随机访问,则使用
ArrayList ,如果要经常从表中间插入或删除元素,则应该使用LinkedList 。 -
队列和堆栈的行为是通过
LinkedList 提供的。 -
Map 是一种将对象(而非数字)与对象相关联的设计。 HashMap 专为快速访问而设计,而 TreeMap 保持键始终处于排序状态,所以没有 HashMap 快。 LinkedHashMap 按插入顺序保存其元素,但使用散列提供快速访问的能力。 -
Set 不接受重复元素。 HashSet 提供最快的查询速度,而 TreeSet 保持元素处于排序状态。 LinkedHashSet 按插入顺序保存其元素,但使用散列提供快速访问的能力。 -
不要在新代码中使用遗留类
Vector ,Hashtable 和 Stack 。
浏览一下
简单集合分类
可以看到,实际上只有四个基本的集合组件: Map , List ,
虚线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的虚线表示特定的类实现了一个接口。实心箭头表示某个类可以生成箭头指向的类的对象。例如,任何
下面的示例展示了各种不同的类在方法上的差异。实际代码来自泛型章节,在这里只是调用它来产生输出。程序的输出还展示了在每个类或接口中所实现的接口:
// collections/CollectionDifferences.java
import onjava.*;
public class CollectionDifferences {
public static void main(String[] args) {
CollectionMethodDifferences.main(args);
}
}
/* 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]
*/
除entrySet()
和 values()
方法来产生
请注意,标记接口
从面向对象的继承层次结构来看,这种组织结构确实有些奇怪。但是,当了解了
尽管存在这些问题,但
-
许多语言,例如
Perl ,Python 和Ruby ,都有集合的本地支持。 ↩︎ -
这里是操作符重载的用武之地,
C++ 和C# 的集合类都使用操作符重载生成了更简洁的语法。 ↩︎ -
在泛型章节的末尾,有个关于这个问题是否很严重的讨论。但是,泛型章节还将展示
Java 泛型远不止是类型安全的集合这么简单。 ↩︎ -
remove()
是一个所谓的“可选”方法(还有其它这样的方法) ,这意味着并非所有的Iterator 实现都必须实现该方法。这个问题将在附录:集合主题中介绍。但是,标准 Java 库集合实现了remove()
,因此在附录:集合主题章节之前,都不必担心这个问题。 ↩︎ -
这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个堆
) ,但它们也可以在删除时选择最重要的元素。 如果一个对象在队列中等待时,它的优先级会发生变化,那么算法的选择就很重要。 ↩︎ -
有些人鼓吹不加思索地创建接口对应一个类甚至每个类中的所有可能的方法组合。 但我相信接口的意义不应该仅限于机械地重复方法组合,因此我倾向于看到增加接口的价值才创建它。 ↩︎
-
这在
Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明,Java 的设计者们更加务实了。 ↩︎ -
下面是译者绘制的
↩︎Java 集合框架简图,黄色为接口,绿色为抽象类,蓝色为具体类。虚线箭头表示实现关系,实线箭头表示继承关系。