注:该系列文章总结自JDK1.9源码的文档注释和源码,我对源码的部分中文注释已经同步到Github:https://github.com/Dodozhou/JDK
Collection的第二层接口和抽象类
Collection第二层常用接口、类继承体系:
Collection继承体系的第二层,即List、AbstractCollection、Set和Queue的常用直接子类或接口如图所示。一一说明如下:
- AbstractList:实现自List接口和AbstractCollection抽象类。该类是List接口的框架性实现,用来减小实现list接口的难度。该类的所有直接实现都是以“random access”的方式访问元素(简而言之就是可以通过下标访问元素,如数组)。对于需要以链表方法是组织元素的集合,应该优先考虑继承AbstractSequentialList。注:AbstractSequentialList也是一个抽象类,且是该类的子类。
- AbstractSet:实现自Set接口和AbstractCollection抽象类。该类是Set接口的一个框架性实现,目的是为了减小实现Set接口的难度。除了一些独有的特性外(如Set不允许插入重复元素),继承该类等同于继承AbstractCollection。
- AbstractQueue:实现自Queue接口和AbstractCollection抽象类。该类是Queue接口的一个框架性实现,目的是为了减小实现Queue接口的难度。该类不允许放入null值。
- SortedSet:SortedSet能保持元素整体有序。元素根据自身的原始顺序或指定的Comparator进行大小比较。迭代器遍历的顺序使升序的。该类为利用有序性额外提供几个方法。
- Deque:实现自Queue接口。该接口定义了一种支持两端进行删除和插入的线性集合,即双端队列。Deque是“double ended queue”的缩写,读作“deck”。
AbstractList
AbstractCollection中定义了一些Collection通用操作。该类在这些基本操作的基础上做了什么工作呢?
- 对List接口的通过下标插入和删除元素的4种方法进行了抽象:public abstract E get(int index);public E set(int index, E element);public void add(int index, E element);public E remove(int index); get方法是抽象方法,其他3种直接在内部抛出了UnsupportedOperationException。
- 重写了继承自AbstractCollection的add(E e)、clear()、Iterator()方法。
- 对List接口的indexOf(Object o)、lastIndexOf(Object o)、addAll(int index, Collection<? extends E> c)等方法进行了初步实现:
public int indexOf(Object o) {
ListIterator<E> it = listIterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return it.previousIndex();
} else {
while (it.hasNext())
if (o.equals(it.next()))
return it.previousIndex();
}
return -1;
}
- 定义了私有迭代器内部类private class Itr implements Iterator<E>、private class ListItr extends Itr implements ListIterator<E>、static final class RandomAccessSpliterator<E> implements Spliterator<E>并实现了其内部功能。
- 定义并实现了一些新的方法,如removeRange(int fromIndex, int toIndex):
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
- 定义并实现了SubList类:private static class SubList<E> extends AbstractList<E> ,该列可以用来获取List的一段视图。注意,对视图的操作都是调用原List的方法,因此这些操作的结果都会映射到原List中。并且,使用了modeCount进行快速失败检测。
AbstractSet
该类是Set接口的一个框架性实现,目的是为了减小实现Set接口的难度。除了一些独有的特性外(如Set不允许插入重复元素),继承该类等同于继承AbstractCollection。注意,该类没有重写任何AbstractCollection的方法,它仅仅添加了equals和hashCode的实现。
它做了什么工作呢?
重写了来自Object的equals(Object)和HashCode()方法,定义了一个集合整体的equals和hashCode逻辑:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
if (c.size() != size())
return false;
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
重写了equals方法,用来判断传入的对象是否和本集合“等同”。当传入的对象o也是一个set,从且两个集合的大小相等,一个集合中的所有元素也存在于另一个集合中(注意,没有要求顺序也相同,因为Set是不保证顺序的)。
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}
集合的hashCode即使所有元素HashCode的和这和AbstractList的hashCode不同,因为set无序,所以不用考虑顺序。而AbsctractList的所有实现都是有序的,它的它的hashCode方法将元素的位置也考虑了进去,具体为:
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
重写了AbstractCollection的removeAll(Collection<?> c)方法
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
if (size() > c.size()) {
for (Object e : c)
modified |= remove(e);
} else {
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
}
return modified;
}
哪个集合较小,就使用哪个集合的迭代器进行遍历。如果本Set较小,那么使用本集合的迭代器进行遍历,并使用iterator.remove方法进行删除.如果传入的集合较小,那么使用传入集合的迭代器对其进行遍历,调用本集合的remove方法进行删除。
### AbstractQueue
实现自Queue接口和AbstractCollection抽象类。该类是Queue接口的一个框架性实现,目的是为了减小实现Queue接口的难度。该类不允许null值。扩展该类的队列实现必须最低限度地定义一个offer方法,它不允许插入空元素,以及相应的peek、poll、size和iterator方法。该类做了哪些工作呢?
- 重写了继承自AbstractCollection的add(E)、clear()和addAll(Collection
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public void clear() {
while (poll() != null)
;
}
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
- 实现了Queue接口的remove()和element()方法
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
SortedSet 接口
SortedSet能保持元素整体有序。元素根据自身的原始顺序或指定的Comparator进行大小比较。迭代器遍历的顺序使升序的。该类为利用有序性额外提供几个方法。
所有被插入Set中的元素都必须实现Comparable接口,或者通过构造器传入一个Comparator。对任意的两个元素e1、e2,都需要能通过e1.compareTo(e2)或者comparator.compare(e1, e2)进行比较。
所有通用的有序set实现类都必须拥有4个标准构造器:
- 一个无参构造器用于创建一个空的set,根据元素的自然顺序进行排序。
- 只含有一个Comparator参数的构造器。用于创建一个空的set,根据指定的比较器进行排序
- 只含有一个Collection类型元素的构造器。创建一个包含此元素的set,根据这个元素的自然顺序进行排序
- 只包含一个SortedSet类型元素的构造器。创建一个新的和传入set相同的元素和顺序的SortedSet。
由于接口中不能包含构造器,因此上面的要求并不能强制要求。
有几个方法可以返回指定范围的子集合。这个范围是半开半闭的(包含开始点,不包含结束点)。如果你需要指定一个闭区间,可以按照以下方式:
SortedSet<String> sub = s.subSet(low, high+"\0");
同样的方法可以用来构造一个全开范围:
SortedSet<String> sub = s.subSet(low+"\0", high);
该接口新定义了5个方法:
- subSet(E,E):获取指定范围(前闭后开)的子集合
- headSet(E toElement):获取从头部到toElement范围(不包括)内的子集合
- tailSet(E fromElement):从formElement(包括)到尾部的子集合
- first():获取第一个元素
- last():获取最后一个元素
Deque 双端队列
该接口定义了一种支持两端进行删除和插入的线性集合。Deque是“double ended queue”的缩写。绝大多数的Deque实现对元素的数量没有固定的限制。此接口同时支持容量受限和不限的双端队列。同Queue一样,Deque的所有的操作都有两种形式:失败了抛异常或者返回一个标志。但是该类多了一组从另一端进行操作的方法。它的方法列表如下:
First Element (Head) | Last Element (Tail) | |||
Throws exception | Special value | Throws exception | Special value | |
Insert | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
Remove | removeFirst() | pollFirst() | removeLast() | pollLast() |
Examine | getFirst() | peekFirst() | getLast() | peekLast() |
当Deque被当做Queue使用时,元素从Deque的尾部插入,头部移除,相应的方法对应如下:
{@code Queue} Method | Equivalent {@code Deque} Method |
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
Deque也可以当做栈使用,当Deque被当做Stack使用时,元素从Deque的头部插入,头部移除,相应的方法对应如下:
Stack Method | Equivalent {@code Deque} Method |
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |