- 13.1.1
Java 将 interfaces 于 implementations 分离。
例如 Queue 是一个 interface。通常有两种实现方法: circular array, linked list, 分别对应 ArrayDeque 类 和 LinkedList 类。 从实现上看,circular array 比 linked list 更高效,但是容量有限。选择 linked list 则对象数量没有上限。利用接口类型去存放对象:
Queue<Customer> expressLaneArray = new ArrayDeque<Customer>(100);
Queue<Customer> expressLaneLL = new LinkedList<Customer>();
这样做的好处是,可以轻松地改为另一种不同的实现。
Java 中也有以 Abstract 开头的集合类,例如 AbstractQueue。这是为类库的实现者设计的。我们也可以通过 extends 这些 Abstract 集合类来实现自己的集合类。
- 13.1.2
Collection 是最基本的集合类接口。它包含2个基本的方法:
public interface Collection<E> {
boolean add(E element);
Iterator<E> iterator();
}
iterator(迭代器) 方法用于返回一个实现了 Iterator 接口的对象。Iterator 接口包含3个方法:
public interface Iterator<E> {
E next();
boolean hasNext();
void remove();
}
next() 可以逐个访问集合中的每个元素,但若到了集合末尾,则会抛出 NoSuchElementException. 因此在调用 next 之前要调用 hasNext() :
Collection<String> c = ...
Iterator<String> iter = c.iterator();
while (iter.hasNext()) {
String element = iter.next();
...
}
也可以用 for each 的写法遍历集合:
for (String element : c) {
...
}
for each 写法可以与任何实现了 Iterable 接口的对象工作。Iterable 接口:
public interface Iterable<E> {
Iterator<E> Iterator();
}
Collection 接口 extends Iterable 接口,由上面我们也知道 Collection 接口包含 Iterator<E> Iterator() 方法。 所以 for each 写法对标准库的任何集合都成立。
对于 Iterator 迭代访问的顺序是基于不同的集合类型而定的。例如 ArrayList 的迭代,从index 0 开始,递增。对于 HashSet 的迭代是随机的,无法预知元素出现的次序。如果想按照特定的顺序,可以用 TreeSet 或者 LinkedHashSet.
删除元素,ierator.remove() 将会删除上次调用 next() 时返回的元素。因为从逻辑上,删除一个元素前先看一下它是什么是符合意义的。所以应该先调用next(), 返回了这个元素,知道它是什么,才决定去 remove. 实际上,调用 remove 前必须调用 next, 否则不合法:
Iterator<String> iter = c.iterator();
iter.next(); // skip over the first element
iter.remove(); // delete the first element
Iterator<String> iter = c.iterator();
iter.remove(); // call remove without next, error
Iterator<String> iter = c.iterator();
iter.next();
iter.remove();
iter.next();
iter.remove(); // work
13.2.1 链表
在 Java 中的LinkedList 类是双向的 (doubly linked list).
若使用 LinkedList 类的 add 方法,就是像链表的尾部添加一个元素。如果想向链表中间的某个位置添加元素,就要依赖于 iterator 的 add 方法。但是只有对有序的集合使用 Iterator 的 add 才有意义。例如 Set 中的元素本身就是无序的,那么使用 iterator 的 add 是没有意义不合逻辑的。因此, Iterator 这个接口是没有 add 方法的。 ListIterator 才有 add 方法。 因为 List 是双向的(例如 LinkedList), 所以 ListIterator 也提供向前遍历,所以有:
E previous();
boolean hasPrevious();
与 next() 和 hasNext() 对应。
ListIterator 的 add 方法是在 迭代器位置之前添加一个新元素。可以这样看待:
| ABC
A | BC
AB | C
ABC |
这里回顾一下迭代器的 remove 方法。remove 是删掉了迭代器刚刚越过 (skip over)的元素。如果是使用 next,则删掉了迭代器左侧的元素,但是若使用 previous, 则删掉了迭代器右侧的元素。
关于迭代器的 set 方法。set 是将一个新元素取代 next 或 previous 方法返回的上一个元素(也就是刚越过的元素):
ListIterator<String> iter = list.listIterator();
String old = iter.next();
iter.set(new); // set the first to a new value
最后是多个迭代器访问一个集合产生的问题。假设一个迭代器修改集合,另外一个对其遍历。遍历的那个迭代器指向一个元素前面,这个元素却被负责修改的迭代器删除了,那么遍历的迭代器就是无效的:
List<String> list = ...
ListIterator<String> iter1 = list.listIterator();
ListIterator<String> iter2 = list.listIterator(); // at this point, iter1 and iter2 both at begining
iter1.next();
iter1.remove(); // first element is removed by iter1
iter2.next; // ConcurrentModificationException here
另外需要注意的是,linkedlist 的 index access 复杂度是 O(n)。所以 list.get(n) 的效率很低,下面是一个效率极低的访问链表的代码:
for (int i = 0; i < list.size(); i++) {
list.get(i)...
}
13.2.3 HashSet
13.2.4 TreeSet
TreeSet 是有序的,底部使用红黑树实现。添加的过程比 HashSet 要慢。
13.2.8 HashMap
HashMap 本身不属于 Collection,但是可以用下面3个 Collection 来表达:
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V> > entrySet();
Set<String> keys = map.keySet();
for (String key : keys) {
...
}
for (Map.Entry<String, Employee> entry : staff.entrySet) {
String key = entry.getKey();
Employee value = entry.getValue();
}
13.3.3 集合和数组转换
Set<String> ts = new HashSet<String>();
ts.add("Ammy");
ts.add("Bob");
ts.add("Carl");
String[] arr = ts.toArray(new String[0]);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
String[] arr = new String[3];
arr[0] = "Ammy";
arr[1] = "Bob";
arr[2] = "Carl";
Set<String> set = new HashSet<String>(Arrays.asList(arr));
Iterator<String> iter = set.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}