注:本文基于JDK 1.7
1 概述
Java提供了一个丰富的集合框架,这个集合框架包含了许多接口、虚拟类和实现类。这些接口和类提供了丰富的功能,能够满足基本的聚合需求。下图就是这个框架的整体结构图:
可以看见,这个框架非常大,大到吃惊的地步。这个图的左面部分是接口,右面部分是类,中间的线代表了右面的类实现了左面的哪些接口。比如,AbstractList类实现了List接口,那么继承自AbstractList类的子类都实现了这个接口。还有,如果一个类实现了一个接口,那么这个类也实现了这个接口的所有父接口。比如,TreeSet类实现了Deque接口,那么TreeSet类也实现了Queue接口、Collection接口和Iterable接口(接口Collection扩展了Iterable,这里没显示出来)。
图中红色的类表示抽象类,大部分抽象类都以Abstract开头,除了Dictionary。绿颜色的类表示遗留类,这些类在Java一开始的时候就已经存在了。
2 接口与实现分离
Java集合类库中将接口(interface)与实现(implementation)分离。这里以队列(queue)为例。
队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以查找队列中的元素个数。即队列中的元素按照先进先出的元素使用队列。
如果我们设计queue的接口,可能这样设计:
interface Queue<E> { void add(E element); E remove(); int size(); }
这里给出了三个基本的方法。这个接口没有给出队列应该是如何实现的。通常,队列的实现方式有两种:一个是使用循环数组,一个是使用链表。
使用循环数组时需要指出对头head和队尾tail;使用链表时也需要给出头和尾:
class CircularArrayQueue<E> implements Queue<E> { CircularArrayQueue(int capacity){...} public void add(E element){...} public E remove(){...} private E[] elements; private int head; private int tail; } class LinkedListQueue<E> implements Queue<E> { LinkedListQueue(){...} public void add(E element){...} public E remove(){...} public int size(){...} private Link head; private Link tail; }
上面的只是我们自己实现的简单的队列,Java类库中没有这两个类。当需要使用队列时,可以使用LinkedList类,这个类实现了Queue接口。
当在程序中使用队列时,一旦构建了集合就不需要知道究竟使用了哪种实现。因此,只有在构建集合对象时,使用具体的类才有意义。可以使用接口类型存放集合的引用:
Queue<Employee> employee=new CircularArrayQueue<>(100); employee.add(new Employee("Harry"));
利用这种方式,一旦改变了想法,可以轻松地使用另一种不同的实现。只需要对程序的一个地方做出修改,即调用构造器的地方。如果觉得LinkedListQueue是个更好的选择,就可以这样修改:
Queue<Employee> employee=new LinkedListQueue<>(); employee.add(new Employee("Harry"));
接口本身没有给出具体的实现方式,因此也不能给出哪种实现更符合实际应用。这样,选择哪种实现方式就需要由程序员选择。Java类库中实现了很多的类,每个类都有各自的特性以及适用场景,也许选择了某个实现方式在某个特性上更加优秀,但也可能在另一个特性上付出了代价。如何平衡好各个性能的代价,需要类库使用者自己把握。
在上图中,我们可以发现很多红色的以Abstract开头的类,这些类都是抽象类,这些类是为类库的实现者设计的,这些类中实现了一些基本的方法。如果想要实现自己的队列类,你会发现扩展AbstractQueue类比实现Queue接口更方便。
3 Java类库中的集合接口和迭代器接口
在Java类库中,集合类的基本接口是Collection接口。这个接口中有如下两个方法:
boolean add(E element); Iterator<E> iterator();
当然,除了这两个方法外还有其它的方法,会在后面介绍。
add方法用于向集合中添加元素,如果添加元素确实改变了集合就返回true,如果集合没有发生变化就返回false。
iterator方法用于返回一个实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。
下面来介绍一下Java类库中重要的迭代器。
Iterator迭代器接口有三个方法:
public interface Iterator<E> { E next(); boolean hasNext(); void remove(); }
通过反复调用next方法,可以逐个访问集合中的每个元素。但是如果达到了集合的末尾,next方法将抛出一个NoSuchElementException异常。因此,在调用next方法之前应该调用hasNext方法。如果迭代器对象还有多个供访问的元素,这个方法就返回true。因此,可以使用下面的方法访问集合中的所有元素:
Collection<String> c=...; Iterator<String> it=c.iterator(); while(it.hasNext()) { String element=it.next(); do something with element }
从Jave SE 5.0起,还可以使用for each循环访问集合中的所有元素:
for(String element : c) { do something with element }
编译器只是将这个for each循环翻译为带有迭代器的循环。
for each循环可以与任何实现了Iterable接口的对象一起工作,这个接口只包含一个方法:
public interface Iterable<E> { Iterator<E> iterator(); }
Collection接口扩展了Iterable接口,所以对于标准库中的任何集合都可以使用for each循环。
需要注意的是,元素被访问的顺序取决于集合的类型。如果对ArrayList进行迭代,迭代器将会从索引0开始,每迭代一次,索引值加1。然而,如果访问HashSet中的元素,每个元素将会按照某种随机的次序出现。虽然可以确定在迭代过程中能够遍历所有的元素,但却无法预知元素被访问的次序。
Java集合类库中的迭代器与其它类库中的迭代器在概念上有着重要的区别。C++的迭代器是根据数组索引建模的。如果给定这样一个迭代器,就可以查看指定位置上的元素,就像知道数组索引i就可以查看数组元素a[i]一样。不需要查找元素,就可以将迭代器向前移动一个位置。这与不需要执行查找操作就可以通过i++将数组索引向前移动一样。但是,Java迭代器不是这样操作的。查找操作和位置变更是紧密相连的。查找一个元素的唯一方法是调用next,而在执行查找操作的同时,迭代器的位置随之向前移动。
即,可以将迭代器看做一个位置,这个位置在两个元素之间。而next操作会改变这个位置。但调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。
Iterator接口的remove方法用于删除上次调用next方法返回时的元素。也就是说,remove操作和next操作具有依赖性,如果没有调用next方法而调用remove方法是非法的,会抛出一个IllegalStateException异常。下面是删除集合中的第一个元素:
Iterator<String> it=c.iterator(); it.next(); it.remove();
如果删除两个相邻的元素,下面的方法是错误的:
it.remove(); it.remove();
必须先调用next方法越过待删除的元素:
it.remove(); it.next(); it.remove();
有一个不怎么恰当的比喻,可以将迭代器看做光标:
Collection<String> c=ArrayList<>(); c.add("a"); c.add("b"); c.add("c"); Iterator<String> it=c.iterator();
这时,迭代器的位置如下:
| a b c
当调用next方法,光标就会后移,然后返回刚才越过的元素:
it.next();
此时,迭代器的位置如下:
a | b c
并返回元素a。
如果要删除元素,删除的行为就像后退键(Backspace)一样,删除光标后面(以右为前)的元素:
it.remove();
此时,迭代器的位置如下:
| b c
和后退键不同的是,如果迭代器的后面即使还有元素,没有调用next方法也不能删除。
由于Collection接口和Iterator接口都是泛型接口,可以编写操作任何集合类型的实用方法。其实,Collection接口中有很多方法:
public interface java.util.Collection<E> extends java.lang.Iterable<E> { public abstract int size(); public abstract boolean isEmpty(); public abstract boolean contains(java.lang.Object); public abstract java.util.Iterator<E> iterator(); public abstract java.lang.Object[] toArray(); public abstract <T> T[] toArray(T[]); public abstract boolean add(E); public abstract boolean remove(java.lang.Object); public abstract boolean containsAll(java.util.Collection<?>); public abstract boolean addAll(java.util.Collection<? extends E>); public abstract boolean removeAll(java.util.Collection<?>); public boolean removeIf(java.util.function.Predicate<? super E>); public abstract boolean retainAll(java.util.Collection<?>); public abstract void clear(); public abstract boolean equals(java.lang.Object); public abstract int hashCode(); public java.util.Spliterator<E> spliterator(); public java.util.stream.Stream<E> stream(); public java.util.stream.Stream<E> parallelStream(); }
有很多的方法的含义都很明确,这里不做过多的解释。
当然,如果实现Collection接口的每一个类都要实现这些例行方法将是一件很烦人的事。为了能够让实现者更容易地实现这个接口,Java类库提供了一个AbstractCollection类,在这个类里提供了一些例行方法,这样,一个具体的集合类就可以扩展AbstractCollection类而不需要实现所有的例行方法了,并可以覆盖里面的方法。
4 Java类库中的接口
下图给出了Java类库中的所有接口:
其中,Collection和Map是集合框架的两个主要接口,所有的集合类都实现了这两个接口中的一个。Iterator接口和ListIterator接口是迭代器接口,而ListIterator接口提供了更丰富的操作,这个接口会在List列表中介绍。RandomAccess接口是一个标签接口,也就是说这个接口没有任何方法,但是可以用这个接口标注某个类,然后检查一个类是否支持随机访问。
在随后的介绍中,会详细介绍这些接口的方法和使用。
5 Java类库中的类
下面是Java类库中的所有实现类:
其中红颜色的是抽象类。可以明显的分为两个部分,一个集合(Collection),一个映射(Map)。
这些是常用的类,再加上一些专用的类,比如:EnumSet、LinkedHashSet、EnumMap、LinkedHashMap、IdentityHashMap和WeakHashMap,一共14类,它们的特点如下:
集合类型 | 描述 |
ArrayList | 一种可以动态增长和伸缩的索引序列 |
LinkedList | 一种可以在任何位置进行高效插入和删除操作的有序序列 |
ArrayDeque | 一种用循环数组实现的双端队列 |
HashSet | 一种没有重复元素的无序集合 |
TreeSet | 一种有序集 |
EnumSet | 一种包含枚举类型值的集 |
LinkedHashSet | 一种可以记住元素插入顺序的集 |
PriorityQueue | 一种允许高效删除最小元素的集合 |
HashMap | 一种存储键/值关联的数据结构 |
TreeMap | 一种键值有序排列的映射表 |
EnumMap | 一种键值属于枚举类型的映射表 |
LinkedHashMap | 一种可以记住键值项添加次序的映射表 |
WeakHashMap | 一种其值无用武之地后可以被垃圾回收器回收的映射表 |
IdentityHashMap | 一种使用==而不是equals比较键值的映射表 |
对于有序无序、元素可重复和元素不可重复,特点总结如下,注意,对于Map,考察的是键的值是否可重复:
6 更多
在后序的分析中,会给出这些具体类和集合的介绍:
2、Java集合(二):List列表
3、Java集合(三):Set集合
4、Java集合(四):Queue队列
5、Java集合(五):专用Set和专用Map
6、Java集合(六):Java集合框架
7、Java集合(七):Java集合中的算法与遗留类