选择不同的数据结构,程序性能会有很大的不同。每当我们需要在巨量数据中快速搜索元素,或者在有序序列中快速插入删除元素,以及建立之间的关联的时候,Java集合类就能帮上大忙。 Java集合框架中的接口 - 图1

1 接口与实现分离

Java的集合类实现了功能完善的数据结构。与常见的数据结构类库一样,Java集合类库也将接口(interface)与实现(implementation)分离。
接口与实现分离有什么好处。假设有一个Queue接口,用来描述队列。队列要包含这些方法:在尾部添加元素,在头部删除元素,查询元素个数。当需要获得队列里的元素,应该遵循先进先出的原则。队列有两种实现方式:使用循环数组,或者使用链表,实现类分别是 CircularArrayQueue 和 LinkedListQueue(实际上没有这两个类)。在程序中构造一个队列时,用接口变量来存放对象,如:

  1. Queue<Customer> expressLane = new CircularArrayQueue<>(100);
  2. expressLane.add(new Customer("Harry"));

以后要是改变了想法,觉得 LinkedListQueue 是更好的选择,只需要修改代码中调用构造器的地方。

  1. Queue<Customer> expressLane = new LinkedListQueue<>(100);
  2. expressLane.add(new Customer("Harry"));

2 Collection 接口

Collection 是集合类的基本接口,它有两个基本方法:

  1. public interface Collection<E> {
  2. boolean add(E element);
  3. Iterator<E> iterator();
  4. . . .
  5. }

add 方法向集合中添加元素,添加成功则返回 true,没加进去就返回 false。
iterator 方法返回一个实现了 Iterator 接口的对象,可以利用这个对象依次访问集合中的元素。
Collection 接口有许多实用方法,所有的实现类都必须提供这些方法。为了能更容易地实现这个接口,Java类库提供了一个 AbstractCollection 类,它实现了很多实用方法,这样就能通过继承 AbstractCollection 类来方便地实现这个接口。
Collection的hashCode方法应该类似于下面这样,用集合中每个元素的哈希码来计算集合对象的哈希码。这样就保证了 c1.equals(c2)c1.hashCode()==c2.hashCode() 的结果相同。

  1. int hashCode = 1;
  2. for (E e : collection)
  3. hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
java.util.Collection
方法签名 用途
boolean add(E element) 添加一个元素,如果这个调用改变了集合,返回true
boolean addAll(Collection<? extends E> other) 添加other中的所有元素,如果这个调用改变了集合,返回true
void clear() 删除所有元素
boolean contains(Object obj) 如果集合中包含了与obj相等的对象,返回true
boolean containsAll(Collection<?> other) 如果此集合包含了other集合的所有元素,返回true
boolean isEmpty() 如果集合中没有元素,返回true
Iterator iterator() 返回一个用于访问集合中各个元素的迭代器
default Spliterator spliterator() 返回一个包含集合中所有元素的 Splierator
int size() 返回集合中现有的元素个数
boolean remove(Object obj) 删除集合中等于obj的元素,如果有匹配的对象被删除,返回true
boolean removeAll(Collection<?> other) 从集合中删除other中存在的元素,如果这个调用改变了集合,返回true
default boolean removeIf(Predicate<? super E> filter) 从集合中删除满足fileter条件的元素,如果这个调用改变了集合,返回true
boolean retainAll(Collection<?> other) 删除此集合中与other中的元素不同的元素,如果这个调用改变了集合,返回true
Object[] toArray() 返回包含这个集合中所有对象的数组
T[] toArray(T[] arrayToFill) 返回包含这个集合中所有对象的数组。如果arrayToFill足够大,就将集合中的元素填入数组中,数组剩余空间填null;否则返回一个新数组,其类型为T,长度等于集合中元素的个数,所有元素都填进去。如果Collection有顺序,则被填充的数组也按相同的顺序排列元素。
default T[] toArray(IntFunction generator) 返回包含这个集合中所有对象的数组。返回的数组由给定的生成函数来分配。
defualt Stream parallelStream() 返回一个可能并行的Stream对象,以此集合中的元素为源
default Stream stream() 返沪一个有序的Stream对象,以此集合中的元素为源

3 Iterator 接口

包含四个方法:

java.util.Iterator
方法签名 用途
boolean hasNext() 如果存在下一个可访问的元素,返回true
E next() 返回将要访问的下一个对象,并把指针向前移动一位。如果已经达到了列表尾部,将抛出NoSuchElementException
void remove() 删除上次访问的对象。这个方法必须紧跟在next方法之后执行。如果上次访问之后集合已经发生了变化,这个方法将抛出IllegalStateException
default void forEachRemaining(Consumer<? super E> action) 对余下的每个元素执行给定的操作,直到没有更多元素,或这个操作抛出异常

遍历集合对象中所有元素的正确做法是,先获得一个 Iterator,当 hasNext 返回 true 时反复调用 next。

  1. Collection<String> c = . . .;
  2. Iterator<String> iter = c.iterator();
  3. while (iter.hasNext()) {
  4. String element = iter.next();
  5. . . .
  6. }

更方便的做法是用“for each”循环,它能处理任何实现了 Iterable 接口的对象。编译器将 for each 循环转换为带 Iterator 的循环,等价于上面的代码。Collection 接口则是直接继承了 Iterable 接口。Iterable 接口只包含一个抽象方法:

  1. public interface Iterable<E> {
  2. Iterator<E> iterator();
  3. . . .
  4. }

访问元素的顺序取决于集合类型,如果迭代处理一个ArrayList,将从索引0开始,往后按1递增;如果访问HashSet,访问顺序是随机的,但也能遍历所有元素。Iterator 的查找操作和位置紧密耦合,只能调用 next 来查找一个元素,调用之后,迭代器就会越过这个元素并返回这个元素,然后到达下一个元素。如果想要删除一个元素,必须先越过它,也就是必须先调用 next 查找这个元素,然后再调用 remove 删除。删除相邻的元素就要交替调用 next 和 remove。

4 ListIterator

ListIterator 除了继承了 Iterator 的所有方法外,还增加了反向遍历,查询当前位置下标,插入元素等方法

java.util.ListIterator
方法签名 用途
void add(E e) 插入一个元素到指针的上一个位置。插入之后,调用previous()会返回刚刚被插入的元素。可以多次调用add,元素会被依次添加到列表中,指针会相应的移到最后一个添加的元素之后。这个方法假定一定会把元素添加进去,而不返回boolean类型的值。
void remove() 删除刚刚访问的一个元素。remove必须紧跟在 next 或者 previous 之后调用,中间不能调用add。
void set(E e) 替换刚刚访问的一个元素。set必须紧跟在 next 或者 previous 之后调用,中间不能调用add 或 remove
boolean hasNext() 如果还有下一个元素,返回true。
E next() 返回下一个元素,并把指针向下移动一个位置
int nextIndex() 返回下一个元素的整数索引,如果指针在末尾,返回的就是列表长度
boolean hasPrevious() 反向遍历列表,如果存在上一个元素,返回true。这时候调用previous会返回一个元素,而不是抛异常。
E previous() 返回上一个元素,并把指针反向移动一个位置
int previousIndex() 返回上一个元素的整数索引,如果指针在开头,会返回-1

集合类的 iterator 和 listIterator 方法返回的迭代器是fail-fast的。这意味着如果调用 iterator 和 listIterator 之后,在其它地方修改了集合对象的结构(比如调用对象本身的add, remove,或者被另一个迭代器修改了),迭代器就会抛 ConcurrentModificationException。但是这个异常不能保证百分百地按期望抛出,这是做不到的,这个异常只能被用来检测bug。

调用 set 方法不被视为修改集合对象的 #结构#,所以不会fail-fast。可以为一个list生成多个迭代器,所有迭代器都可以调用set方法修改当前list的内容。

5 List 接口

List 是有序集合,可以用迭代器访问(顺序访问),也可以用整数索引来访问(随机访问)。List 在Collection的基础上扩展了通过索引进行随机访问的方法:

ajva.util.List
方法签名 用途
void add(int index, E element) 向指定的位置插入元素,后面的元素全部往后移一位。
boolean addAll(int index, Collection<? extends E> c)
E get(int index)
E set(int index, E element)
E remove(int index)
查找,替换,删除给定位置的元素,操作成功就返回操作之前该位置的元素
int indexOf(Object obj)
int lastIndexOf(Object obj)
返回列表中给定对象出现的第一个和最后一个位置索引
List subList(int fromIndex, int toIndex) 返回指定范围的一个子列,子列直接引用源列表中的元素。可以在子列中进行结构性的修改,但是子列之外的结构性修改是没有定义的。
ListIterator listIterator()
ListIterator listIterator(int index)
返回迭代器对象,或者从给定的索引位置开始返回

静态工厂方法 List.of 和 List.copyOf 可以方便地生成不可变的 List 对象。这些 List 对象不能进行添加、删除、替换元素的操作;不允许有null元素;元素的次序跟原来的集合中元素的次序相同,或者跟参数的次序相同;不能对元素使用相等运算符,因为元素的内存地址是不确定的,可能直接引用源List中的元素,也可能是一个拷贝。不可变的List对象,如果其中包含的元素本身是可变的,对元素的字段进行修改就可能改变源List里的元素的字段。
static List copyOf(Collection<? extends E> coll)
static List of() // 创建空的不可变列表
static List of(E… elements) // 创建包含任意个元素的不可变列表
List 类的实例方法 boolean equals(Object o) 保证,只有当参数 o 是List,且元素数目以及元素顺序都相同,才返回true。

值得注意的是,List接口实现了一个默认的排序方法: default void sort(Comparator<? super E> c) 这个方法接受一个比较器对象,用它来对list中的元素进行两两比较。如果比较器为null,那么列表里的元素必须自己实现Comparable接口。调用sort方法的对象本身必须是可被修改的。 提示:Comparator 接口只有一个抽象方法 int compare(T o1, To2); Comparable 接口只有一个抽象方法int compareTo(T o); 。

由数组实现的有序集合可以快速地随机访问,适合用List本身的方法;而链表实现的有序集合适合用Iterator访问,因为随机访问链表非常慢。 为了避免对链表进行随机访问操作,Java有一个标记接口 RandomAccess。它不包含任何方法,专门用来测试一个集合对象是否适合随机访问:

  1. if (c instanceof RandomAccess) {
  2. 使用随机访问算法
  3. } else {
  4. 使用顺序访问算法
  5. }

6 Set 接口

Set 接口和 Collection 接口等同,但是 Set 中不允许添加重复元素。实现 Set 接口的时候,要适当的定义 equals 方法:只要两个 Set 包含同样的元素就认为他们相等,而不要求顺序相同。hashCode方法也要保证元素相同而顺序可能不同的 Set 会得到相同的哈希值。
Set 也实现了静态工厂方法 List.of 和 List.copyOf,作用跟List里面的类似,不同在于Set不允许重复元素。
Set的实例方法 boolean equals(Object o) 只有当参数 o 是Set,且o里面的元素跟Set实例的元素完全一样,才返回true。

为什么**要在子接口里声明跟父接口一样的方法**?举例来说,Set和Collection源码基本是一样的,但是Set的方法添加了其它的约定,这些约定就要写在方法的注释里,所以要再次声明。这样也可以一目了然的看出Set包含的方法,不用去Collection找。

7 Queue 和 Deque 接口

在Collection的基础上,Queue增加了特别的插入,提取,查找方法。Queue的实现类既可以按先进先出来组织元素,也可以按后进先出来组织元素。

java.util.Queue
操作失败就抛异常 操作失败就返回特殊值
插入 boolean add(E e)
在尾部插入元素,如果队列容量足够,返回true,否则抛异常
boolean offer(E e)
在尾部插入元素,队列容量足够,返回true,否则返回false
删除 E remove()
返回并删除队列头部的元素,队列为空抛异常
E poll()
返回并删除队列头部的元素,如果队列为空,返回null
检索 E element()
返回但不删除队列头部的元素,队列为空抛异常
E peek()
返回但不删除队列头部的元素,队列为空返回null

Deque的意思是double end queue,它能在队列的两端都进行插入和删除操作。类似于Queue,Deque的方法也有两类,这里不做详细说明。

java.util.Deque
操作失败抛异常 操作失败返回特殊值
插入 void addFirst(E e)
void addLast(E e)
boolean offerFirst(E e)
boolean offerLast(E e)
删除 E removeFirst()
E removeLast()
E pollFirst()
E pollLast()
检索 E getFirst()
E getLast()
E peekFirst()
E peekLast()

Deque也可以当成Stack来使用,它提供了push和pop方法。

8 Map 接口

Map就是映射,用来存放键 / 值对。如果提供了键,就能查找到对应的值。Map中的键不能重复,每个键对应唯一的一个值。添加元素时,必须同时提供键和值。
Mapkey 如果是字段可变的类,那么就不要修改Map对象里的元素的键,这种行为会导致键和值的对应关系被打乱,equals 和 hashCode 方法也会不正常。所以禁止将Map里的键设为 Map。但是值可以设为 Map。
集合框架不认为映射是一个Collection。不过,可以得到映射的视图View,这是实现了Collection接口或某个子接口的对象。有三种视图:键集、值集合、以及键/值对集。因为键和键/值对不允许重复。下面的三个方法分别返回这三个视图:
Set keySet()
Collection values
Set> entrySet()
对视图可以调用remove,但是不能添加元素。对视图的修改会影响到原来的Map,反之亦然

java.util.Map
方法签名 用途
V get(Object key) 返回与key关联的对象,如果找不到key,返回null(实现类可以禁止key为null)
default V getOrDefault(Object key, V defaultValue) 返回与key关联的对象,如果找不到key,返回defaultValue。
V put(K key, V value) 将关联的一对键和值放到Map里。如果之前没有这个键,返回null。如果这个键已存在,新的值将取代这个键关联的旧值,然后返回键关联的旧值。(实现类可以禁止key或value为null)
default V putIfAbsent(K key, V value) 如果key没有关联值或关联了null,就将key跟value关联,并返回null;如果key有关联值oldValue,则返回oldValue,不修改Map
void putAll(Map<? extends K, ? extends V> entries) 将给定的Map对象中的所有条目,添加到这个Map中。
V remove(Object key) 根据 key 删除内容
boolean containsKey(Object key) 查找Map中是否存在给定的键
boolean containsValue(Object value) 查找Map中是否存在给定的值
void clear()
boolean isEmpty()
清空 Map 集合中的内容
判断是否为空
Collection values()

Set keySet()
Set> entrySet()
返回所有value的一个集合视图,可以从这个集合删除元素,但不能添加;
返回所有key的一个集视图;
返回所有Map.Entry的一个集视图。
default void forEach(BiConsumer<? super K,? super V> action) 对Map中所有条目应用这个操作
default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果key没有关联值或者关联了null,就添加给定的键值对;如果key有非null关联值oldValue,就把oldValue和value传给函数,把函数的返回值与key关联;如果函数返回的是null,就删除这个key。如果函数抛异常,则Map不会被修改。这个函数里面不能修改Map。
default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 将每一个条目的值替换为function中的函数在这个条目上的返回值。
java.util.Map.Entry
K getKey()
V getValue()
返回此映射条目的键;
返回此映射条目的值;
V setValue(V newValue) 用新值替换此映射条目的值,并返回原来的值

9 SortedMap

方法签名 用途
Comparator<? super K> comparator() 返回对键进行排序的比较器。如果键是用Comparable接口的compareTo方法进行比较,则返回null
K firstKey()
K lastKey()
返回Map中的最小键
返回Map中的最大键