We often use the subString method to split String objects. At the same time, we can also use subList, subMap, and subSet to split List, Map, and Set. processing, but this segmentation has certain flaws.
First, let’s look at the following example:
public static void main(String[] args) { List<Integer> list1 = new ArrayList<Integer>(); list1.add(1); list1.add(2); //通过构造函数新建一个包含list1的列表 list2 List<Integer> list2 = new ArrayList<Integer>(list1); //通过subList生成一个与list1一样的列表 list3 List<Integer> 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)); }
This example is very simple , it is nothing more than regenerating a list the same as list1 through the constructor and subList, then modifying list3, and finally comparing list1 == list2?, list1 == list3?. According to our conventional thinking, it should be like this: because list3 adds a new element through add, then it must not be equal to list1, and list2 is constructed through list1, so it should be equal, so the result should be:
list1 == list2:true list1 == list3: false
First of all, regardless of whether the result is correct or not, let’s look at the source code of subList:
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
, whether toIndex is legal. If it is legal, a subList object will be returned directly. Note that a parameter this is passed when generating the new object. This parameter is very important because it represents the original list.
/** * 继承AbstractList类,实现RandomAccess接口 */ private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; //列表 private final int parentOffset; private final int offset; int size; //构造函数 SubList(AbstractList<E> 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; } //set方法 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; } //get方法 public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(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; } }
The SubLsit is the internal class of ArrayList. It is the same as ArrayList and is inherited. AbstractList and implements the RandomAccess interface. It also provides commonly used list methods such as get, set, add, and remove. But its constructor is a bit special. There are two places to pay attention to in the constructor:
1. this.parent = parent; and parent is the list passed in front, In other words, this.parent is a reference to the original list.
2. this.offset = offset + fromIndex; this.parentOffset = fromIndex;. At the same time, it even passes modCount (fail-fast mechanism) in the constructor.
Let’s look at the get method again. In the get method, return ArrayList.this.elementData(offset + index); this code can clearly show that what get returns is the offset + index position of the original list. Elements. The same principle applies to the add method:
parent.add(parentOffset + index, e); this.modCount = parent.modCount;
The remove method
E result = parent.remove(parentOffset + index); this.modCount = parent.modCount;
It is true that we are here We can judge that the SubList returned by subList is also a subclass of AbstractList. At the same time, its methods such as get, set, add, remove, etc. all operate on the original list. It does not generate a new object like subString. So subList returns only a view of the original list, and all its operations will eventually be applied to the original list.
Then from the analysis here we can conclude that the above result should be exactly the opposite of our above answer: Java细节(3.1):subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上 从上面我们知道subList生成的子列表只是原列表的一个视图而已,如果我们操作子列表它产生的作用都会在原列表上面表现,但是如果我们操作原列表会产生什么情况呢? 该实例如果不产生意外,那么他们两个list的大小都应该都是3,但是偏偏事与愿违,事实上我们得到的结果是这样的: list1正常输出,但是list3就抛出ConcurrentModificationException异常,看过我另一篇博客的同仁肯定对这个异常非常,fail-fast?不错就是fail-fast机制,在fail-fast机制中,LZ花了很多力气来讲述这个异常,所以这里LZ就不对这个异常多讲了(更多请点这里:Java提高篇(三四)—–fail-fast机制)。我们再看size方法: size方法首先会通过checkForComodification验证,然后再返回this.size。 该方法表明当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException。同时我们知道modCount 在new的过程中 “继承”了原列表modCount,只有在修改该列表(子列表)时才会修改该值(先表现在原列表后作用于子列表)。而在该实例中我们是操作原列表,原列表的modCount当然不会反应在子列表的modCount上啦,所以才会抛出该异常。 对于子列表视图,它是动态生成的,生成之后就不要操作原列表了,否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态,要操作就操作子列表: Java细节(3.2):生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常 在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据,可能我们会这样处理: 这个应该是我们大部分人的处理方式吧,其实还有更好的方法,利用subList。在前面LZ已经讲过,子列表的操作都会反映在原列表上。所以下面一行代码全部搞定: 简单而不失华丽!!!!! 以上就是Java提高配(三七)—–Java集合细节(三):subList的缺陷的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)! list1 == list2:false
list1 == list3:true
二、subList生成子列表后,不要试图去操作原列表
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);
//通过subList生成一个与list1一样的列表 list3
List<Integer> list3 = list1.subList(0, list1.size());
//修改list3
list1.add(3);
System.out.println("list1'size:" + list1.size());
System.out.println("list3'size:" + list3.size());
}
list1'size:3
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(Unknown Source)
at java.util.ArrayList$SubList.size(Unknown Source)
at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)
public int size() {
checkForComodification();
return this.size;
}
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
//通过subList生成一个与list1一样的列表 list3
List<Integer> list3 = list1.subList(0, list1.size());
//对list1设置为只读状态
list1 = Collections.unmodifiableList(list1);
三、推荐使用subList处理局部列表
for(int i = 0 ; i < list1.size() ; i++){
if(i >= 100 && i <= 200){
list1.remove(i);
/*
* 当然这段代码存在问题,list remove之后后面的元素会填充上来,
* 所以需要对i进行简单的处理,当然这个不是这里讨论的问题。
*/
}
}
list1.subList(100, 200).clear();