今天在看Java开发手册的时候看到这么一句话:
如果需要对list某个范围内的元素进行操作,可以使用subList,任何对子列表的操作最终都会反映到原列表中。
例如list.subList(0,2).clear;这样的操作便会对原列表进行修改,修改的结果是截取除了0-2位置的元素,下面写下代码试一下:
运行结果:
我们看到原列表已经被修改,那么为什么对子列表的修改会影响到原列表呢,我们进入源码研究一下:
我们进入源码,可以看到除了通过subListRangeCheck方法对参数进行校验检查外,最终返回的是new了一个构造方法(即子列表对象),我们发现subList返回的这个结果并没有重新new一个ArrayList对象,那么SubList与ArrayList有什么关系呢,我们利用IDEA自带的diagrams查看他们之间的继承关系,如图:
我们发现SubList与ArrayList之间并没有继承关系,这就说明了为什么使用subList截取list后不能强转为ArrayList。
下面探究一下调用subList之后为什么对子列表的操作会反映到原列表中:
首先写一个demo重现一下这个现象:
运行结果:
我们可以看到我们通过对子列表进行set操作修改了子列表,这个修改操作已经反映到原列表中,这是为什么呢,我们进入set方法查看:
主要看下面两行:
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
这两行代码即诠释了为什么对子列表的操作会反映到原列表,这里的elementData即存放原列表数据的数组,我们看到这里的set操作是对offset + index位置上的元素进行赋值操作的,而offset就是子列表第一个元素在原列表中的位置,所以offset + index就是要修改的元素在原列表的位置
然后我们进入ArrayList中的SubList构造方法:
我们可以看到子列表这个构造函数,通过调试我们发现他的offset是fromIndex的值,即在原列表中的位置,所以返回的子列表对象实际是原列表中[fromIndex,toIndex)范围内的元素视图,所以对于子列表的操作均被反映到原列表中。
同样对于原列表的操作均会影响到子列表,所以在使用subList时要注意对原列表的操作是否会对子列表产生影响,尤其是删除原列表元素时可能会产生ConcurrentModificationException异常。