SubList
大家好,今天Tony给大家讲个SubList
转化的坑。
这个错误真的会被忽略,大家好好的看看,这个错误我们生产环境还真的遇到过。
集合类型相信大家都很熟悉,在Java中ArrayList使用的场景非常普遍。我们今天主要看的是ArrayList中的subList 方法。
首先我们来看看源码
Returns a view of the portion of this list between the specified {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
在jdk的源码中清楚的写明了返回的是一个new SubList
,方法的注释上面写的是返回一个View,可以理解为视图吗?
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
接下里我们再细品SubList
,源码
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;
}
}
SubList 是ArrayList中的一个内部类,继承了AbstractList,实现了RandomAccess,从上面的代码中可以看到,在SubList这个构造方法中还是直接引用的父类中的元素,只是单纯的将截取的索引重新赋值了一下。
使用场景
public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("兔子");
add("托尼");
add("啊");
}};
List<String> subList = names.subList(0, 3);
System.out.println(subList);
}
上面的代码输出结果
[兔子, 托尼, 啊]
在什么情况下会报错呢?接下来再看个例子,把上面的代码简单修改下,让数据返回ArrayList
public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("兔子");
add("托尼");
add("啊");
}};
ArrayList<String> subList = (ArrayList)names.subList(0, 3);
System.out.println(subList);
}
上面的代码直接抛出异常了
Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList
为什么不能直接转换为ArrayList呢?上面的源码已经显示了,SubList只是一个内部类,它继承AbstractList 和ArrayList 根本都没有关系,所以直接转化会报Cast异常。
ModificationException
SubList同样具有集合原属都方法比如添加、删除等。我截取部分源码。
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;
}
```
上面等源码中每一个方法都包含有一个 checkForComodification 方法。
这个方法是有什么作用呢?
```java
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
源码中写的很清楚,判断原始类型,可以理解为父类型原始的ArrayList和当前的SubList方法中的元素个数做比较,如果不一样就报异常。 1、 对subList视图做数据的删除
public static void main(String[] args) {
List<String> namesList = new ArrayList<String>() {{
add("兔子");
add("托尼");
add("啊");
}};
System.out.println("namesList原始的:== ==>" + namesList);
List<String> subList = namesList.subList(0, 2);
System.out.println("subList截取的:== ==>" + subList);
//删除SubList第2个元素
subList.remove(1);
System.out.println("subList删除的:== ==>" + subList);
System.out.println("namesList删除的:== ==>" + namesList);
}
上面的代码运行正常输出结果
namesList原始的:== ==>[兔子, 托尼, 啊]
subList截取的:== ==>[兔子, 托尼]
subList删除的:== ==>[兔子]
namesList删除的:== ==>[兔子, 啊]
2、 对ArrayList做数据的删除
public static void main(String[] args) {
List<String> namesList = new ArrayList<String>() {{
add("兔子");
add("托尼");
add("啊");
}};
System.out.println("namesList原始的:== ==>" + namesList);
List<String> subList = namesList.subList(0, 2);
System.out.println("subList截取的:== ==>" + subList);
//删除ArraList第2个元素
namesList.remove(1);
System.out.println("subList删除的:== ==>" + subList);
System.out.println("namesList删除的:== ==>" + namesList);
}
输出结果报异常了
namesList原始的:== ==>[兔子, 托尼, 啊]
subList截取的:== ==>[兔子, 托尼]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)
at java.util.AbstractCollection.toString(AbstractCollection.java:454)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
当我们对父元素ArrayList中对数据进行删除操作的时候,我们会发现subList会报一个 ConcurrentModificationException异常,这个异常是对数据比较发现元素被更改过,可以理解为脏数据吗?
总结
1、 SubList 和 ArrayList之间没有任何关系
2、千万不要将SubList转化为ArrayList会报转换异常
3、对SubList视图元素对修改会影响原始对父ArrayList中对数据。
4、对ArrayList数据对删除添加等修改,SubList会报Modification异常
其实我们可以理解下,SubList理解为一个视图,其实就是一个内部类,它的实现就是在原始的ArrayList中改变了截取的索引位置。
对视图的操作结果会反映到原始的ArrayList中,如果对原始的ArrayList做数据的添加删除操作,不好意思此刻的SubList已经报异常了。
通俗一点,可以修改儿子,不能修改父亲。
SubList转化为ArrayList可以用Guava中的封装方法
Lists.newArrayList(subList)