选择不同的数据结构,程序性能会有很大的不同。每当我们需要在巨量数据中快速搜索元素,或者在有序序列中快速插入删除元素,以及建立键
与值
之间的关联的时候,Java集合类就能帮上大忙。
1 接口与实现分离
Java的集合类实现了功能完善的数据结构。与常见的数据结构类库一样,Java集合类库也将接口(interface)与实现(implementation)分离。
接口与实现分离有什么好处。假设有一个Queue接口,用来描述队列。队列要包含这些方法:在尾部添加元素,在头部删除元素,查询元素个数。当需要获得队列里的元素,应该遵循先进先出的原则。队列有两种实现方式:使用循环数组,或者使用链表,实现类分别是 CircularArrayQueue 和 LinkedListQueue(实际上没有这两个类)。在程序中构造一个队列时,用接口变量来存放对象,如:
Queue<Customer> expressLane = new CircularArrayQueue<>(100);
expressLane.add(new Customer("Harry"));
以后要是改变了想法,觉得 LinkedListQueue 是更好的选择,只需要修改代码中调用构造器的地方。
Queue<Customer> expressLane = new LinkedListQueue<>(100);
expressLane.add(new Customer("Harry"));
2 Collection 接口
Collection 是集合类的基本接口,它有两个基本方法:
public interface Collection<E> {
boolean add(E element);
Iterator<E> iterator();
. . .
}
add 方法向集合中添加元素,添加成功则返回 true,没加进去就返回 false。
iterator 方法返回一个实现了 Iterator 接口的对象,可以利用这个对象依次访问集合中的元素。
Collection 接口有许多实用方法,所有的实现类都必须提供这些方法。为了能更容易地实现这个接口,Java类库提供了一个 AbstractCollection 类,它实现了很多实用方法,这样就能通过继承 AbstractCollection 类来方便地实现这个接口。
Collection的hashCode方法应该类似于下面这样,用集合中每个元素的哈希码来计算集合对象的哈希码。这样就保证了 c1.equals(c2)
和c1.hashCode()==c2.hashCode()
的结果相同。
int hashCode = 1;
for (E e : collection)
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 |
返回一个用于访问集合中各个元素的迭代器 |
default 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() | 返回包含这个集合中所有对象的数组 |
返回包含这个集合中所有对象的数组。如果arrayToFill足够大,就将集合中的元素填入数组中,数组剩余空间填null;否则返回一个新数组,其类型为T,长度等于集合中元素的个数,所有元素都填进去。如果Collection有顺序,则被填充的数组也按相同的顺序排列元素。 | |
default |
返回包含这个集合中所有对象的数组。返回的数组由给定的生成函数来分配。 |
defualt Stream |
返回一个可能并行的Stream对象,以此集合中的元素为源 |
default 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。
Collection<String> c = . . .;
Iterator<String> iter = c.iterator();
while (iter.hasNext()) {
String element = iter.next();
. . .
}
更方便的做法是用“for each”循环,它能处理任何实现了 Iterable 接口的对象。编译器将 for each 循环转换为带 Iterator 的循环,等价于上面的代码。Collection 接口则是直接继承了 Iterable 接口。Iterable 接口只包含一个抽象方法:
public interface Iterable<E> {
Iterator<E> iterator();
. . .
}
访问元素的顺序取决于集合类型,如果迭代处理一个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 |
返回指定范围的一个子列,子列直接引用源列表中的元素。可以在子列中进行结构性的修改,但是子列之外的结构性修改是没有定义的。 |
ListIterator ListIterator |
返回迭代器对象,或者从给定的索引位置开始返回 |
静态工厂方法 List.of 和 List.copyOf 可以方便地生成不可变的 List 对象。这些 List 对象不能进行添加、删除、替换元素的操作;不允许有null元素;元素的次序跟原来的集合中元素的次序相同,或者跟参数的次序相同;不能对元素使用相等运算符,因为元素的内存地址是不确定的,可能直接引用源List中的元素,也可能是一个拷贝。不可变的List对象,如果其中包含的元素本身是可变的,对元素的字段进行修改就可能改变源List里的元素的字段。
static
static
static
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。它不包含任何方法,专门用来测试一个集合对象是否适合随机访问:
if (c instanceof RandomAccess) {
使用随机访问算法
} else {
使用顺序访问算法
}
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中的键不能重复,每个键对应唯一的一个值。添加元素时,必须同时提供键和值。
Map
集合框架不认为映射是一个Collection。不过,可以得到映射的视图View,这是实现了Collection接口或某个子接口的对象。有三种视图:键集、值集合、以及键/值对集。因为键和键/值对不允许重复。下面的三个方法分别返回这三个视图:
Set
Collection
Set
对视图可以调用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 Set Set |
返回所有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中的最大键 |