ArrayList中有两个转换为数组的方法,Object[] toArray()和<T> T[] toArray(T[] a)。
两个方法的唯一区别就是返回的数据类型不同,最终都是这个方法:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
因为前一篇说过,实际ArrayList中保存数据的是一个Object类型的数组,所以默认ArrayList转换成的数组就是Object类型。
ArrayList中还有一个比较常用的内部类SubList,利用方法:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
获取一个当前ArrayList的类似于视图的
SubList对象,从[fromIndex,toIndex)这么一个左闭右开的区间范围,对于原ArrayList和这个SubList的“non-structural changes”(非结构性改变)会互相影响,这里非结构性的改变指不改变大小。
如果这个SubList发生了结构性的变化,那么原ArrayList也会相应发生改变;如果原ArrayList发生了结构性变化,从语义上来说,SubList变成undefined。
从代码实现角度上来说,针对SubList的操作,都会先进行以此判断,比较当前SubList的修改次数与原ArrayList修改次数及modCount是否相同。如果不相同则会抛出ConcurrentModificationException异常。并且在针对SubList操作之后,都会同步修改原ArrayList的modCount值。
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
所以在使用SubList时我们需要谨慎一些,尽量在只需要针对原ArrayList的部分进行操作时,使用SubList,并且注意不要修改原ArrayList大小。
从jdk1.8开始,对于ArrayList的遍历多了一种方式,其内部实际也是for循环遍历,只不过采用了函数编程的思想和写法。
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
在jdk1.8还新增加一个新特性,ArrayListSpliterator实现了Spliterator这个接口,也是1.8新增加的特性。
在1.8中stream内部实现都会接受一个Spliterator参数,
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
接口Spliterator有如下几个方法:
当还有需要处理的元素时,那么对该元素使用action处理,并且返回true,否则返回false。
boolean tryAdvance(Consumer<? super T> action);
对于剩下的元素遍历处理,实际调用tryAdvance方法,直到没有元素需要处理了,即tryAdvance返回false:
default void forEachRemaining(Consumer<? super T> action) {
do { } while (tryAdvance(action));
}
分解当前的迭代器,并且返回分解后的结果
Spliterator<T> trySplit();
主要是影响并发的线程数
long estimateSize();
获取当前迭代器的特性,不同特性会对以上方法有不同的影响,其中特性会有很多:
int characteristics();
public static final int ORDERED = 0x00000010;
public static final int DISTINCT = 0x00000001;
public static final int SORTED = 0x00000004;
public static final int SIZED = 0x00000040;
public static final int NONNULL = 0x00000100;
public static final int IMMUTABLE = 0x00000400;
public static final int CONCURRENT = 0x00001000;
public static final int SUBSIZED = 0x00004000;
可以看出来,需要按位或,就可以得到其多有的特性。
我们学习一下ArrayList中是如何使用的,首先是分割迭代器:
public ArrayListSpliterator<E> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null : // divide range in half unless too small
new ArrayListSpliterator<E>(list, lo, index = mid,
expectedModCount);
}
首先getFence会获取到迭代器分割的最高位置,lo为当前遍历的位置,mid为中间值,如果当前迭代器只有一个元素了,不再对其进行分割,否则等分为两部分。我们可以做一个测试。
List<String> ls = Lists.newArrayList("1", "2", "3", "4", "5", "6");
Spliterator<String> spliterator = ls.spliterator();
Spliterator<String> a = spliterator.trySplit();
Spliterator<String> b = a.trySplit();
Spliterator<String> c = b.trySplit();
spliterator.forEachRemaining(s -> System.out.print(s+" "));//4 5 6
System.out.println("-------------------------");
a.forEachRemaining(s -> System.out.print(s+" "));//2 3
System.out.println("-------------------------");
b.forEachRemaining(s -> System.out.print(s+" "));//1
System.out.println("-------------------------");
c.forEachRemaining(s -> System.out.print(s+" "));//NullPointerException
可以看到,迭代器b已经只有一个元素了,如果再继续进行切分会返回null。
计算fence的方法,初始化时fence=-1,则设置为当前ArrayList的大小,以后每次设置为了构造迭代器时的参数,即mid大小:
private int getFence() { // initialize fence to size on first use
int hi; // (a specialized variant appears in method forEach)
ArrayList<E> lst;
if ((hi = fence) < 0) {
if ((lst = list) == null)
hi = fence = 0;
else {
expectedModCount = lst.modCount;
hi = fence = lst.size;
}
}
return hi;
}
再看tryAdvance方法,就是先获取当前迭代器的最大位置,比较一下当前遍历的位置与fence大小,小于代表还有需要处理的元素,则对其执行action;否则代表没有需要处理的元素,返回false即可:
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
int hi = getFence(), i = index;
if (i < hi) {
index = i + 1;
@SuppressWarnings("unchecked") E e = (E)list.elementData[i];
action.accept(e);
if (list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
最后是forEachRemaining方法,这里没有采用默认的循环调用tryAdvance方法,而是自己遍历内部元素,进行处理:
public void forEachRemaining(Consumer<? super E> action) {
int i, hi, mc; // hoist accesses and checks from loop
ArrayList<E> lst; Object[] a;
if (action == null)
throw new NullPointerException();
if ((lst = list) != null && (a = lst.elementData) != null) {
if ((hi = fence) < 0) {
mc = lst.modCount;
hi = lst.size;
}
else
mc = expectedModCount;
if ((i = index) >= 0 && (index = hi) <= a.length) {
for (; i < hi; ++i) {
@SuppressWarnings("unchecked") E e = (E) a[i];
action.accept(e);
}
if (lst.modCount == mc)
return;
}
}
throw new ConcurrentModificationException();
}
最后再打断点调试一下,具体内部是如何工作的。调试内容就不再写了。
未完待续......