jdk 包版本:jdk1.8.0_111
Java标准库自带的java.util
包提供了集合类:Collection
,它是除Map
外所有其他集合类的根接口,它作为一个容器,存储一系列的对象。Map
是键值对的结构,存储一系列的键值对<key, value>。Java 的java.util
包主要提供了以下四种类型的集合:
-
List
:一种有序列表的集合; -
Set
:一种保证没有重复元素的集合; -
Map
:一种通过键值(key-value)查找的映射表集合。 -
Queue
:一种队列容器,其特性与List
相同,但只能从队头和队尾对元素进行操作。
JDK 为集合的各种操作还提供了两个工具类:Collections
和 Arrays
。
Java集合的设计有几个特点:一是实现了接口和实现类相分离,例如,有序表的接口是List
,具体的实现类有ArrayList
,LinkedList
等,二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素。
Collection
之上还继承了Iterable
接口,因此 Java 访问集合总是能统一通过迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
由于 Java 的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:
-
Hashtable
:一种线程安全的Map
实现; -
Vector
:一种线程安全的List
实现; -
Stack
:基于Vector
实现的LIFO
的栈。
还有一小部分接口是遗留接口,也不应该继续使用:
-
Enumeration<E>
:已被Iterator<E>
取代。
需要特别注意的是,Collection 接口的注释头强调,该接口中的所有方法的实现都没有使用任何并发协议的。也就是,实现该接口的类如果采用默认的实现模式,这个过程是线程不安全的。要想实现线程安全,必须在实现类中重写对应的方法,并且在方法实现逻辑中采用适合的并发策略。
在继承 Collection 接口的基础上,List
、Set
和Queue
三大类集合接口根据各自的特点,分别补充了一些新的接口方法,衍生出不同的额外功能,是开发者能够比对各个集合之间的优劣,择优而用。在Collection
接口之下,还定义了一个抽象类AbstractColletion
对该接口进行实现。根据注释的说明,这个抽象类提供了该接口的一个基础实现,降低了开发者实现该接口需要的工作量。在实现一个继承类时,需要提供 iterator( ) 和 size( ) 方法以及其底层支撑方法的实现;如果具体实现的集合类具备可变性,对应的还要重写增删改相关的方法。
继承AbstractCollection
抽象类的抽象类主要有以下三大类集合:
• AbstractList
• AbstractSet
• AbstractQueue
分别对应实现 List,Set,Queue 三大类接口,与 AbstractCollection 类似,这些抽象类为其实现的接口提供了基础的方法实现,简化了继承类实现的开发过程。
List 接口
实现 List 接口的集合类主要包括 Vector,ArrayList,LinkedList 和 AbstractList。其中 Vector 目前已经很少使用,包括其子类 Stack 也基本被弃用,因此在这里不详细讨论。
AbstractList 抽象类
AbstractList 类继承了 AbstractCollection 抽象类,同时实现了 List 接口,为接口内定义的方法提供了基础实现。值得注意的是,开发者继承该抽象类时,不需要再提供迭代器的具体实现,在 AbstractList 内部定义了两个内部类Itr
和ListItr
,分别实现了迭代器所需的基本方法。当自定义一个类继承这个抽象类时,通常需要重写一个实现为空的构造方法。
AbstractList 类的内部还定义了两个内部子列表类:SubList
和RandomAccessSubList
。前者定义并且实现了一部分子列表相关的方法;后者继承了前者,并且实现了RandomAccess
接口,说明这个类返回的是允许访问随机索引的子列表实例。
AbstractSequenceList 抽象类
AbstractSequenceList 抽象类继承了 AbstractList,在原基础上限制了访问元素的顺序。顾名思义,它不支持随机访问元素,只能按照顺序进行访问。LinkedList
使用链表实现,只支持顺序访问,所以继承了AbstractSequenceList
抽象类;而像ArrayList
这样的类需要支持随机访问,所以继承了AbstractList
抽象类。
ArrayList 类
LinkedList 类
Queue 接口
Queue 队列接口有主要有两种不同的实现:单向队列(AbstractQueue)和双向队列(Deque)。不管是哪种实现,都是基于 Queue 定义的两套接口方法,这两套方法在运行失败时,会有不同的处理策略,具体如下:
插入方法 | 删除方法 | 查找方法 | 失败策略 |
add() | remove() | get() | 抛出异常 |
offer() | poll() | peek() | 返回 false 或者 null |
值得注意的是,poll()
方法成功运行时,是返回了队列头的元素,并在原队列中删除该元素,而不只是单纯的删除元素。
Deque 接口
Deque 接口继承了 Queue 接口,在原有的基础上增加了具体操作队头和队尾元素的方法。该接口的实现类有两种,分别是LinkedList
类和ArrayDeque
类,两者的主要区别在于,前者采用链表实现双端队列,而后者使用数组实现双端队列。
LinkedList 类
ArrayDeque 类
ArrayDeque 在日常使用不多,它使用数组实现了一个无界的双端队列。它与LinkedList
的区别在于,LinkedList
采用链表实现双端队列,而ArrayDeque
使用数组实现双端队列。它的最小容量为 8,初始化时默认容量为 16,如果初始化时指定一个容量大小的数值,亦或者传入一个集合类,则通过 allocateElements( ) 方法,将初始容量选为比指定容量大的一个最小 2 的幂。
添加新元素时,如果添加后队列已满,也就是head==tail
,就会调用内部的 doubleCapacity( ) 方法,重新创建一个数组,容量为原来数组的两倍。如果容量数值通过左移加倍导致越界,那么就会抛出异常。因此需要控制数组的容量不能过大,不能超过。
PriorityQueue 类