JDK11
Collection接口
public interface Collection<E> extends Iterable<E> {
作为一元容器的顶级接口,实现类包括线性表(List,Queue,Stack)和集合(Set)
List能够包含重复的元素,Set包含的是不重复元素
- Bags和multisets可以直接实现该接口
- Set和List子接口继承了Collection接口
- 实现其子接口的类应该提供两个构造函数:
1.空的构造函数
2.拷贝构造函数 - 没有重写的方法需要抛出"UnsupportedOperationException"
接口实现的方法有:
boolean add(Ee);
boolean addAll(Collection<? extends E> c);
boolean remove(Object o);
// 移除满足条件的元素,移除条件由filter决定,返回值指示是否移除成功
// 传入的参数是一个函数式的接口
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while(each.hasNext()) {
if(filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
//匹配则移除
boolean removeAll(Collection<?> c);
//不匹配则移除
boolean retainAll(Collection<?> c);
void clear();
boolean contains(Object o);
boolean containsAll(Collection<?> c);
Object[] toArray();
// eg:
// String[] y = new String[SIZE];
// y = x.toArray(y);
<T> T[] toArray(T[] a);
// JDK 11引入了一个新的toArray方法
// 可以传入一个生成集合数据的函数式表达式
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
//不能保证返回容器元素的顺序
Iterator<E> iterator();
//jdk1.8引入可分割迭代器的概念,为了并行遍历元素
//可以理解为把集合分为N段,N个线程同时遍历N段,可以保证线程安全
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
// 返回当前容器的顺序数据流
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
// 获取当前容器的并行数据流
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
// 返回当前容器的元素数量
int size();
// 判断当前容器是否为空
boolean isEmpty();
boolean equals(Object o);
int hashCode();
AbstractCollection
// 一元容器的抽象实现
public abstract class AbstractCollection<E> implements Collection<E> {
- 如果要实现不可变集合,请继承该类并且提供iterator方法
- 如果要实现可变集合,请重写add方法(否则抛出UnsupportedOperationException异常)
- 并且iterator还要实现remove方法
- 具体而言这个类干的事情就是实现了大部分Collection接口的方法,那么用户在自定义自己的集合类的时候就方便很多了
public <T> T[] toArray(T[] a) {
// Estimate size of array; be prepared to see more or fewer elements
int size = size();
// 如果传入数组大小大于等于当前容器元素个数直接把返回的新数组赋予传入数组,否则用反射创建一个当前元素个数大小的新数组
T[] r = a.length >= size ? a : (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator();
for(int i = 0; i<r.length; i++) {
// 如果已经不存在下个元素了
if(!it.hasNext()) { // fewer elements than expected
if(a == r) {
r[i] = null; // null-terminate
} else if(a.length<i) {
// 创建一个长度为i的新数组返回
return Arrays.copyOf(r, i);
} else {
//从r复制i个元素到a中
System.arraycopy(r, 0, a, 0, i);
if(a.length>i) {
a[i] = null;
}
}
return a;
}
r[i] = (T) it.next();
}
// more elements than expected
return it.hasNext() ? finishToArray(r, it) : r;
//finishToArray处理的是iterator中的元素比传入的数组大小要大
```java
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
int i = r.length;
while(it.hasNext()) {
int cap = r.length;
if(i == cap) {
// 扩容
int newCap = cap + (cap >> 1) + 1;
// overflow-conscious code
if(newCap - MAX_ARRAY_SIZE>0) {
newCap = hugeCapacity(cap + 1);
}
r = Arrays.copyOf(r, newCap);
}
r[i++] = (T) it.next();
}
// trim if overallocated
return (i == r.length) ? r : Arrays.copyOf(r, i);
}
List接口
线性表接口
public interface List<E> extends Collection<E> {
List接口实现了一个当前线性表的增强迭代器,可以双向迭代
ListIterator<E> listIterator();
AbstractList
线性表的抽象实现
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
继承AbstractCollection,实现List接口
- 如果要实现一个不可修改的集合,只需要继承这个类,并且实现get,size()方法
- 如果要实现可修改集合,需要重写set()方法,如果可以动态变更大小,还需要重写add,remove方法
protected transient int modCount = 0;
线性表结构改变的次数
在使用迭代器的时候,一旦发现这个对象的mcount和迭代器中存储的mcount不一样那就抛异常,说明迭代过程中线性表大小或者结构发生了变化。这种机制叫fail-fast机制
写的非常优美的equals:
public boolean equals(Object o) {
if(o == this)
return true;
if(!(o instanceof List))
return false;
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while(e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if(!(o1 == null ? o2 == null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
1.先判断传入的参数与自身对象引用是否相同
2.再比较传入参数类型是否为List
3.然后分别取要比较的两个对象的线性表迭代器依次比较各元素是否相同
4.最后还要判断两个iterator是否都遍历完成
- 线性表对象的hashcode如何实现?
public int hashCode() {
int hashCode = 1;
for(E e : this)
hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
return hashCode;
}
- 迭代器一般怎么重写呢?可以看看源码
成员分别有:
cursor:下一个要迭代的元素的索引
lastRet:上一次迭代完的元素的索引
expectedModCount: 线性表结构改变次数
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
// 同时更新cursor和lastRet
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
//迭代器的remove删除的是刚遍历完的元素
public void remove() {
if(lastRet<0) {
throw new IllegalStateException();
}
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if(lastRet<cursor) {
cursor--;
}
lastRet = -1;
expectedModCount = modCount;
} catch(IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
// 对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常
final void checkForComodification() {
if(modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
- 能够实现双向迭代的线性表迭代器又是怎么实现的呢?
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
}
//只要当前元素索引不是第一个肯定就会有previous
public boolean hasPrevious() {
return cursor != 0;
}
public E previous() {
checkForComodification();
try {
//i为下一个要遍历的元素的前一个元素
int i = cursor - 1;
E previous = get(i);
//cursor = cursor-1
//lastRet = i
lastRet = cursor = i;
return previous;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
public void set(E e) {
if(lastRet<0) {
throw new IllegalStateException();
}
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch(IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
AbstractList.this.add(i, e);
// 为何设为-1?
lastRet = -1;
//cursor++
cursor = i + 1;
expectedModCount = modCount;
} catch(IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
这里看到有的函数签名用到了泛型,
顺便讲讲泛型中 <? extends E> 和 <? super E>的区别
-
前者说明接受的参数必须是E类或者E的子类, 表面对象的类上限是E
-
后者说明接收的参数必须是E类或者是E的父类,表面对象类下限是E
-
PECS原则
PECS法则:生产者(Producer)使用extends,消费者(Consumer)使用super
频繁往外读取内容的,适合用上界Extends。
经常往里插入的,适合用下界Super。
1、生产者
如果你需要一个提供E类型元素的集合,使用泛型通配符<? extends E>。它好比一个生产者,可以提供数据。
2、消费者
如果你需要一个只能装入E类型元素的集合,使用泛型通配符<? super E>。它好比一个消费者,可以消费你提供的数据。
3、既是生产者也是消费者
既要存储又要读取,那就别使用泛型通配符。
这里的源码就很好地体现了这一点:
我们来看RandomAccessSpliterator这个类的的消费者参数
public boolean tryAdvance(Consumer<? super E> action) {
if(action == null) {
throw new NullPointerException();
}
int hi = getFence(), i = index;
if(i<hi) {
index = i + 1;
action.accept(get(list, i));
checkAbstractListModCount(alist, expectedModCount);
return true;
}
return false;
}
23万+

被折叠的 条评论
为什么被折叠?



