我们经常使用subList()、subMap()、subSet()来对 List、Map、Set 进行分割处理,但这个分割存在有一些坑。
一、subList 返回仅仅只是一个视图
public static void main(String[] args) {
List list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
// 通过构造函数新建一个包含list1的列表 list2
List list2 = new ArrayList<>(list1);
// 通过subList生成一个与list1一样的列表 list3
List list3 = list1.subList(0, list1.size());
// 修改list3
list3.add(3);
System.out.println("list1 == list2:" + list1.equals(list2));
System.out.println("list1 == list3:" + list1.equals(list3));
}
理论分析结果:因为 list3 通过 add 新增了一个元素,那么它肯定与 list1 不等,而 list2 是通过 list1 构造出来的,所以应该相等,结果应该是:
list1 == list2:true
list1 == list3:false
然而事实是:
list1 == list2:false
list1 == list3:true
下面是源码时间:
首先我们看下ArrayList类的subList()方法java.util.ArrayList#subList
:
public List subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
subListRangeCheck() 方法是判断 fromIndex、toIndex 是否合法,如果合法就直接返回一个 subList 对象。这里需要注意2点:
- 在产生该 new 该对象的时候传递了一个参数 this ,该参数非常重要,因为他代表着原始 list。
- SubList类是ArrayList的一个内部类,这个类很特别。
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 extends E> c) {
return addAll(this.size, c);
}
public boolean addAll(int index, Collection extends E> 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 != ArrayList.SubList.this.size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= ArrayList.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)];
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.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;
ArrayList.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 ArrayList.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();
}
}
- 第一个特别点是它的构造函数,在该构造函数中有2个地方需要注意:
-
this.parent = parent;
这里的parent 就是在前面传递过来的 list,也就是说 this.parent 就是原始 list 的引用。 -
this.offset = offset + fromIndex;this.parentOffset = fromIndex;
。同时在构造函数中它将 modCount(fail-fast机制)也传递过来了。
- 第二个特别点是它的普通方法,
- 先看 get 方法,在 get 方法中
return ArrayList.this.elementData(offset + index);
这段代码可以清晰表明 get 所返回就是原列表offset + index
位置的元素。 - 再看add 方法:
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e); // 注意这里
this.modCount = parent.modCount;
this.size++;
}
- 和remove方法:
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index); // 注意这里
this.modCount = parent.modCount;
this.size--;
return result;
}
由以上源码,我们可以判断 subList() 方法返回的 SubList 同样也是 AbstractList 的子类,同时它的方法如 get、set、add、remove 等都是在原list上面做操作,而不是生成一个新的对象。所以 subList()方法返回的只是原list的一个视图,它所有的操作最终都会作用在原列表上。