首先看一个关于subList的问题:下面这段代码的输出结果是什么?
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
List sub = list.subList(0, 2);
sub.add(5);
for (Integer i : list) {
System.out.print(i.intValue() + "\t");
}
如果认为答案是1 2 3,那么就错了,正确答案是1 2 5 3
那么问题是,为什么对sub插入的数据同时会影响到原本的list中
下面从源码角度解释这个问题:
subList方法的确会产生一个新的List不错,然而这个新的List并不完全是一个新的List,也就是说,新的List和原先的List并没有完全脱离,而是通过偏移量对原本的List进行管理,当执行add操作时候,依旧是插入到原先的List当中。
subList方法并不是ArrayList中的方法,而是其父类AbstractList中提供的方法。
它的源码如下:
public List subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ?
new RandomAccessSubList(this, fromIndex, toIndex) :
new SubList(this, fromIndex, toIndex));
}
如果说调用此方法的List是一个RandomAccess实例,将返回一个RandomAccessSubList,否则返回一个SubList。
RandomAccessSubList与SubList均是AbstractList的内部类,而前者很简单,它是后者的子类,区别在于实现了RandomAccess接口,那么最终要通过查看SubList类中的代码才能理解subList方法的具体情况。(在此暂不讨论RandomAccess)
SubList类的构造方法及该类中的变量:
private AbstractList l;
private int offset;
private int size;
private int expectedModCount;
SubList(AbstractList list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
expectedModCount = l.modCount;
}
在做出一系列合法性判断后,只是做了简单的赋值。此处的 l 即为原始的list, offset是fromIndex, size是toIndex - fromIndex,即总长度, expectedModCount初值为l.modCount
在之前代码中:
List sub = list.subList(0, 2);
sub.add(5);
执行这两句代码后,sub执行的其实是SubList中的add方法,那么这个add方法做了什么,如下:
public void add(int index, E element) {
if (index<0 || index>size)
throw new IndexOutOfBoundsException();
checkForComodification();
l.add(index+offset, element);
expectedModCount = l.modCount;
size++;
modCount++;
}
在这个add方法中,首先调用了 l 的add方法,随即修改了size的值,也就是说,SubList只是对原来的List进行了管理,而并不是单独的List
看看它的get方法:
public E get(int index) {
rangeCheck(index);
checkForComodification();
return l.get(index+offset);
}
return的结果是 l 的 index+offset位置的元素值
综上,subList方法获得的List并不是截取出的一个List,而是通过对原来的List做了封装,提供了一些方法来管理,通过偏移量来进行控制,因此一旦改变subList,原来的List同时发生变动。