Arrays.asList和ArrayList的subList用法注意事项

一、Arrays.asList

        日常开发过程中,我们经常用Arrays.asList快速声明一个已知元素的集合。但是在使用过程中需要注意几点:

1、不支持删除和添加操作,会抛UnsupportedOperationException异常。

Arrays.asList和ArrayList的subList用法注意事项_第1张图片

 那么,为什么会出现这种情况呢?不懂就看源码!我们先看下asList方法的实现。

public static  List asList(T... a) {
    return new ArrayList<>(a);
}

咦,乍一看好像没啥毛病,但仔细一看,此处的ArrayList只是Arrays类的内部类,并不是我们常用的java.util包下的ArrayList。虽然也继承了AbstractList抽象类,但是其中并没有重写add和remove方法。所以会直接调用抽象类中的add和remove方法,而抽象类中的这两个方法就直接抛出UnsupportedOperationException异常。

private static class ArrayList extends AbstractList
        implements RandomAccess, java.io.Serializable
{
	private static final long serialVersionUID = -2764017481108945198L;
	private final E[] a;

	ArrayList(E[] array) {
		a = Objects.requireNonNull(array);
	}

	@Override
	public int size() {
		return a.length;
	}

	@Override
	public Object[] toArray() {
		return a.clone();
	}

	@Override
	@SuppressWarnings("unchecked")
	public  T[] toArray(T[] a) {
		int size = size();
		if (a.length < size)
			return Arrays.copyOf(this.a, size,
								 (Class) a.getClass());
		System.arraycopy(this.a, 0, a, 0, size);
		if (a.length > size)
			a[size] = null;
		return a;
	}

	@Override
	public E get(int index) {
		return a[index];
	}

	@Override
	public E set(int index, E element) {
		E oldValue = a[index];
		a[index] = element;
		return oldValue;
	}

	@Override
	public int indexOf(Object o) {
		E[] a = this.a;
		if (o == null) {
			for (int i = 0; i < a.length; i++)
				if (a[i] == null)
					return i;
		} else {
			for (int i = 0; i < a.length; i++)
				if (o.equals(a[i]))
					return i;
		}
		return -1;
	}

	@Override
	public boolean contains(Object o) {
		return indexOf(o) != -1;
	}

	@Override
	public Spliterator spliterator() {
		return Spliterators.spliterator(a, Spliterator.ORDERED);
	}

	@Override
	public void forEach(Consumer action) {
		Objects.requireNonNull(action);
		for (E e : a) {
			action.accept(e);
		}
	}

	@Override
	public void replaceAll(UnaryOperator operator) {
		Objects.requireNonNull(operator);
		E[] a = this.a;
		for (int i = 0; i < a.length; i++) {
			a[i] = operator.apply(a[i]);
		}
	}

	@Override
	public void sort(Comparator c) {
		Arrays.sort(a, c);
	}
}

2、根据数组快速声明集合,数组变化,集合也会随之变化;反之亦然。

Arrays.asList和ArrayList的subList用法注意事项_第2张图片

 究其原因,从1中的源码和Objects.requireNonNull的源码可以看出,Arrays类中的内部类ArrayList的构造函数仅是将array的引用传递给内部变量a,所以两者均指向同一个地址,因而二者其一变化也会影响另一个。

二、ArrayList的subList

        subList常用来获取指定索引段的子集合。 但是使用过程中也要注意以下几点

1、修改原集合元素的值,会影响子集合

Arrays.asList和ArrayList的subList用法注意事项_第3张图片

 2、修改原集合的结构(增加或减少元素),会抛出ConcurrentModificationException异常。

但是注意:增加或减少原集合元素的时候,并不报错,只有在调用子集合的时候,才会报错

Arrays.asList和ArrayList的subList用法注意事项_第4张图片

 3、修改子集合元素的值,会影响原集合

Arrays.asList和ArrayList的subList用法注意事项_第5张图片

4、修改子集合的结构(增加或减少元素),会影响原集合

Arrays.asList和ArrayList的subList用法注意事项_第6张图片

         那问题来了,为啥原集合和子集合的修改都会影响对方?更奇怪的是,子集合结构变化并不会引起原集合报错,为啥原集合结构变化会引起子集合报错?不懂就看源码!

(1) 为啥原集合和子集合的修改都会影响对方?

        首先看subList方法的源码,调用的是一个SubList构造函数。

public List subList(int fromIndex, int toIndex) {
	subListRangeCheck(fromIndex, toIndex, size);
	return new SubList(this, 0, fromIndex, toIndex);
}

        继续看SubList类的内容(如下图),发现它竟然是ArrayList的内部类,而且根据构造函数可以看出,并没有重新生成子集合,而只是将原集合的引用传递给自身的变量parent,针对子集合的操作均是对原集合的操作。这就是原集合和子集合的修改都会影响对方的原因。

private class SubList extends AbstractList implements RandomAccess {
	private final AbstractList parent;
	private final int parentOffset;
	private final int offset;
	int size;

	SubList(AbstractList parent,
			int offset, int fromIndex, int toIndex) {
		this.parent = parent;
		this.parentOffset = fromIndex;
		this.offset = offset + fromIndex;
		this.size = toIndex - fromIndex;
		this.modCount = ArrayList.this.modCount;
	}

	public E set(int index, E e) {
		rangeCheck(index);
		checkForComodification();
		E oldValue = ArrayList.this.elementData(offset + index);
		ArrayList.this.elementData[offset + index] = e;
		return oldValue;
	}

	public E get(int index) {
		rangeCheck(index);
		checkForComodification();
		return ArrayList.this.elementData(offset + index);
	}

	public int size() {
		checkForComodification();
		return this.size;
	}

	public void add(int index, E e) {
		rangeCheckForAdd(index);
		checkForComodification();
		parent.add(parentOffset + index, e);
		this.modCount = parent.modCount;
		this.size++;
	}

	public E remove(int index) {
		rangeCheck(index);
		checkForComodification();
		E result = parent.remove(parentOffset + index);
		this.modCount = parent.modCount;
		this.size--;
		return result;
	}

	protected void removeRange(int fromIndex, int toIndex) {
		checkForComodification();
		parent.removeRange(parentOffset + fromIndex,
						   parentOffset + toIndex);
		this.modCount = parent.modCount;
		this.size -= toIndex - fromIndex;
	}

	public boolean addAll(Collection c) {
		return addAll(this.size, c);
	}

	public boolean addAll(int index, Collection c) {
		rangeCheckForAdd(index);
		int cSize = c.size();
		if (cSize==0)
			return false;

		checkForComodification();
		parent.addAll(parentOffset + index, c);
		this.modCount = parent.modCount;
		this.size += cSize;
		return true;
	}

	public Iterator iterator() {
		return listIterator();
	}

	public ListIterator listIterator(final int index) {
		checkForComodification();
		rangeCheckForAdd(index);
		final int offset = this.offset;

		return new ListIterator() {
			int cursor = index;
			int lastRet = -1;
			int expectedModCount = ArrayList.this.modCount;

			public boolean hasNext() {
				return cursor != SubList.this.size;
			}

			@SuppressWarnings("unchecked")
			public E next() {
				checkForComodification();
				int i = cursor;
				if (i >= SubList.this.size)
					throw new NoSuchElementException();
				Object[] elementData = ArrayList.this.elementData;
				if (offset + i >= elementData.length)
					throw new ConcurrentModificationException();
				cursor = i + 1;
				return (E) elementData[offset + (lastRet = i)];
			}

			public boolean hasPrevious() {
				return cursor != 0;
			}

			@SuppressWarnings("unchecked")
			public E previous() {
				checkForComodification();
				int i = cursor - 1;
				if (i < 0)
					throw new NoSuchElementException();
				Object[] elementData = ArrayList.this.elementData;
				if (offset + i >= elementData.length)
					throw new ConcurrentModificationException();
				cursor = i;
				return (E) elementData[offset + (lastRet = i)];
			}

			@SuppressWarnings("unchecked")
			public void forEachRemaining(Consumer consumer) {
				Objects.requireNonNull(consumer);
				final int size = SubList.this.size;
				int i = cursor;
				if (i >= size) {
					return;
				}
				final Object[] elementData = ArrayList.this.elementData;
				if (offset + i >= elementData.length) {
					throw new ConcurrentModificationException();
				}
				while (i != size && modCount == expectedModCount) {
					consumer.accept((E) elementData[offset + (i++)]);
				}
				// update once at end of iteration to reduce heap write traffic
				lastRet = cursor = i;
				checkForComodification();
			}

			public int nextIndex() {
				return cursor;
			}

			public int previousIndex() {
				return cursor - 1;
			}

			public void remove() {
				if (lastRet < 0)
					throw new IllegalStateException();
				checkForComodification();

				try {
					SubList.this.remove(lastRet);
					cursor = lastRet;
					lastRet = -1;
					expectedModCount = ArrayList.this.modCount;
				} catch (IndexOutOfBoundsException ex) {
					throw new ConcurrentModificationException();
				}
			}

			public void set(E e) {
				if (lastRet < 0)
					throw new IllegalStateException();
				checkForComodification();

				try {
					ArrayList.this.set(offset + lastRet, e);
				} catch (IndexOutOfBoundsException ex) {
					throw new ConcurrentModificationException();
				}
			}

			public void add(E e) {
				checkForComodification();

				try {
					int i = cursor;
					SubList.this.add(i, e);
					cursor = i + 1;
					lastRet = -1;
					expectedModCount = ArrayList.this.modCount;
				} catch (IndexOutOfBoundsException ex) {
					throw new ConcurrentModificationException();
				}
			}

			final void checkForComodification() {
				if (expectedModCount != ArrayList.this.modCount)
					throw new ConcurrentModificationException();
			}
		};
	}

	public List subList(int fromIndex, int toIndex) {
		subListRangeCheck(fromIndex, toIndex, size);
		return new SubList(this, offset, fromIndex, toIndex);
	}

	private void rangeCheck(int index) {
		if (index < 0 || index >= this.size)
			throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
	}

	private void rangeCheckForAdd(int index) {
		if (index < 0 || index > this.size)
			throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
	}

	private String outOfBoundsMsg(int index) {
		return "Index: "+index+", Size: "+this.size;
	}

	private void checkForComodification() {
		if (ArrayList.this.modCount != this.modCount)
			throw new ConcurrentModificationException();
	}

	public Spliterator spliterator() {
		checkForComodification();
		return new ArrayListSpliterator(ArrayList.this, offset,
										   offset + this.size, this.modCount);
	}
}

(2)为啥原集合结构变化会引起子集合报错? 

        而细心的同学会发现,除了记录原集合的引用地址,还记录了相对于原集合的偏移量、子集合的大小、原集合当前的modCount。而这个modCount就是导致“原集合结构变化会引起子集合报错”的罪魁祸首。

Arrays.asList和ArrayList的subList用法注意事项_第7张图片 

        首先看modCount的定义:集合被结构性(即改变集合大小的)修改、或者某种扰乱modCount的场景(过程中会产生错误结果的迭代)的次数。所以像增加/删除元素的操作会导致modCount的增加。

        其次细心的同学更会观察到,在SubList的每个方法中都会优先调用一个checkForComodification方法,而这个方法就是用来校验子集合的modCount和原集合的modCount是否一致,若不一致就会抛出ConcurrentModificationException异常。原集合结构发生变化后modCount会增加,而子集合modCount并没有变化,所以会在对子集合进行调用时抛出异常;而子集合的任何变化都是针对原集合进行操作,并且操作完成后会更新原集合和子集合的modCount,所以总是保持一致,从而不会抛出异常。

你可能感兴趣的:(java,java)