第十一章 持有对象--容器
11.1 泛型和类型安全容器
当使用泛型容器时,情况比较复杂,相比较之下预定义类型比较简单。
当指定某个类型作为泛型参数时,也可以将向上转型的对象加入其中。
11.2 基本概念
Java容器类库用途是保存对象,可以划分为两个不同的概念:
- Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与插入顺序相同)。
- Map。一组成对的“键值对”对象,允许使用键来查找值。ArrayList允许使用数字来查找值。映射表允许使用一个对象来查找另一个对象,称为“关联数组”或“字典”。
所有Collection都可以用foreach语法遍历。
11.3 添加一组元素
- 可以调用Collection对象的addAll+Arrays.asList方法
- 或者使用Collections.addAll方法,该方法第一个参数时Collection对象,后面支持普通对象数组、可变参数列表,或者Arrays.asList。
方法一较快,但是方法二的适应新更好一些(键第二段代码,线下实验不会报错了,目前版本jdk-14.0.1)。
需要注意的是Arrays.asList返回的是List,不能调整尺寸。
import java.util.*;
public class AddingGroups {
public static void main(String[] args) {
Collection<Integer> collection =
new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
Integer[] moreInts = { 6, 7, 8, 9, 10 };
collection.addAll(Arrays.asList(moreInts));
// collection.addAll(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 because the
// underlying array cannot be resized.
}
} ///:~
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());
// Won't compile:
List<Snow> snow2 = Arrays.asList(
new Light(), new Heavy());
// Compiler says:
// found : java.util.List<Powder>
// required: java.util.List<Snow>
// Collections.addAll() doesn't get confused:
List<Snow> snow3 = new ArrayList<Snow>();
Collections.addAll(snow3, new Light(), new Heavy());
// Give a hint using an
// explicit type argument specification:
List<Snow> snow4 = Arrays.<Snow>asList(
new Light(), new Heavy());
}
} ///:~
11.4 容器的打印
Map与Collection都有toString方法。
11.5 List
List接口在Collection的基础上添加了大量方法,使得可以在List中插入和移除元素。
- ArrayList,擅长随机访问元素,但是插入和已出元素时较慢。
- LinkedList,插入删除较快,但是随机访问方面较慢。
11.6 迭代器
11.6.1 基本操作
迭代式是一个对象,可以遍历选择序列中的对象。此外迭代器是轻量级对象,创建它的开销很小。Java中迭代器效果如下:
- 调用容器的List对象的iterator()便可以获得相应的迭代器。
- 迭代器只能单向移动。
- 使用next()方法获取序列中的下一个元素。
- 使用hasNext()来检查序列中是否还含有元素。
- 使用remove()方法将迭代器当前的元素删除(jdk-14.0.1版本,该方法运行时会报错)。
public class SimpleIteration {
public static void main(String[] args) {
List<Integer> pets = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
Iterator<Integer> it = pets.iterator();
while(it.hasNext()) {
Integer p = it.next();
System.out.print("i = "+ p + " ");
}
System.out.println();
// A simpler approach, when possible:
for(Integer p : pets)
System.out.print("i = "+ p + " ");
System.out.println();
// An Iterator can also remove elements:
it = pets.iterator();
while(it.hasNext()){
it.next();
it.remove();
}
System.out.println(pets);
}
} /* Output:
i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 0
i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 0
Exception in thread "main" java.lang.UnsupportedOperationException: remove
at java.base/java.util.Iterator.remove(Iterator.java:102)
at chapter11.SimpleIteration.main(SimpleIteration.java:22)
*///:~
11.6.2 优点
- 无需担心元素的数量,可以由next()与hasNext()执行。
- 无需知晓特点的类型(将迭代器向上转型成Iterator)。
import java.util.*;
public class CrossContainerIteration {
public static void display(Iterator<Integer> it) {
while(it.hasNext()) {
Integer p = it.next();
System.out.print(p + " ");
}
System.out.println();
}
public static void main(String[] args) {
ArrayList<Integer> pets = new ArrayList<>();
for (int i=0; i < 10; i++)
pets.add(i);
LinkedList<Integer> petsLL = new LinkedList<Integer>(pets);
HashSet<Integer> petsHS = new HashSet<Integer>(pets);
TreeSet<Integer> petsTS = new TreeSet<Integer>(pets);
display(pets.iterator());
display(petsLL.iterator());
display(petsHS.iterator());
display(petsTS.iterator());
}
} /* Output:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
*///:~
11.6.3 ListIterator
可以向前与向后移动的迭代器。
import java.util.*;
public class ListIteration {
public static void main(String[] args) {
List<Integer> pets = new ArrayList<>();
for(int i=0; i<10; i++)
pets.add(i+1);
// List<Integer> pets = Pets.arrayList(8);
ListIterator<Integer> it = pets.listIterator();
while(it.hasNext())
System.out.print(it.next() + ", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
System.out.println();
// Backwards:
while(it.hasPrevious()){
it.previous();
System.out.print(it.previousIndex() + " ");
}
// System.out.print(it.previous().id() + " ");
System.out.println();
System.out.println(pets);
it = pets.listIterator(3);
while(it.hasNext()) {
it.next();
it.set(0);
it.remove();
}
System.out.println(pets);
}
}
11.7 LinkedList
11.7.1 获取元素
获取表头元素
- getFirst
- element
- peek
11.7.2 删除表头元素
- remove()
- removeFirst()
- poll()
11.7.3 添加元素
- addFirst():向表头添加元素
- add():队尾添加
- offer():队尾添加
- addLast:队尾添加
11.8 Stack
先进后出。
11.9 Set
保证集合内的元素不重复。
- contains():
- containsAll():
- remove()
- removeAll()
11.10 Map
Map提供键值对的映射。
import java.util.*;
public class Statistics {
public static void main(String[] args) {
Random rand = new Random(47);
Map<Integer,Integer> m =
new HashMap<Integer,Integer>();
for(int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
int r = rand.nextInt(20);
Integer freq = m.get(r);
m.put(r, freq == null ? 1 : freq + 1);
}
System.out.println(m);
}
}
可以拓展到多维
Map<Integer, List<Integer>>
11.11 Queue
队列是一个先进先出的容器,即从容器的一端放入事物,然后从一端取出。
LinkedList提供了方法支持Queue的行为,因此可以将LinkedList向上转型为Queue。
- offer():插入元素到队尾。
- peek():在不移除的情况下返回队头,队列为空时返回null。
- element():在不移除的情况下返回队头,队列为空时抛出异常。
- poll():删除并返回队头,队列为空时返回null。
- remove():删除并返回队头,队列为空时抛出异常。
11.11.1 PriorityQueue
优先级队列。该队列会根据优先级顺序自动对元素进行排序。
- 优先级队列允许重复。
- 最小的值拥有最高的优先级。
- 可以提供自己的Comparator来改变排序。
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<Integer>(ints);
// 添加Collections.reverseOrder()来使得优先级队列反序。
priorityQueue = new PriorityQueue<Integer>(
ints.size(), Collections.reverseOrder());
11.12 Collections和Iterator
Collection是描述所有序列容器的共性的根接口。希望接口描述的一个理由是它可以使我们创建更通用的代码。通过接口而非具体的实现来编写代码。
如果编写的方法将接受一个Collection,那么该方法就可以应用于任何实现了Collection的类–这也使得一个新类可以选择去实现Collection接口,以便我们去使用它。
Java选用迭代器而不是Collection来表示容器之间的共性。但是两种方法绑定到了一起,因为事先COllection就意味着提供Iterator方法。
11.13 Foreach与迭代器
Java SE5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator方法,并且Iterator接口被foreach用来在序列中移动。因此创建了任何实现Iterable的类都可以将它应用于foreach语句。
11.13.1 适配器方法的惯用法
当希望在foreach语句中添加一种或多种foreach语句使用这个方法,可以采用适配器模式。即创造新的Iterator实现,并用适配器进行包装。
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.
*///:~
11.13.1 注意事项–Arrays.asList
该方法将创建对原有数组的引用,因此对转化后的数据修改会直接影响原有数组。比较好的办法是创建副本。
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<Integer>(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]
*///:~
11.14 总结
Java提供了大量持有对象的方式:
- 数组将数字与对象关联起来。
- Collection保存单一的元素,而Map保存相关联的键值对。
- 向数组一样,List也建立了数字索引与对象的关联。
- 大量随机访问使用ArrayList,经常插入删除使用LinkedList。
- 各种Queue以及栈的行为,由LinkedList支持。
- Map是一种将对象与对象相关联的设计。
- Set不接受重复元素。
- 新程序中不应该使用过时的Vector、HashTable和Stack。
Java容器简图:
可以看到,只有四种容器:List、Set、Queue和Map。它们各有两个到三个实现版本。
点线框表示接口,实线框表示具体的类。带有空心箭头的点线表示一个特定的类实现了一个接口,实心箭头表示一个某个类可以生成箭头所指的类的对象。