你們好,今天 Tony 給你們講個SubList
轉化的坑。java
這個錯誤真的會被忽略,你們好好的看看,這個錯誤咱們生產環境還真的遇到過。web
集合類型相信你們都很熟悉,在 Java 中 ArrayList 使用的場景很是廣泛。咱們今天主要看的是 ArrayList 中的 subList 方法。app
首先咱們來看看源碼dom
Returns a view of the portion of this list between the specified {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.編輯器
」
在 jdk 的源碼中清楚的寫明瞭返回的是一個new SubList
,方法的註釋上面寫的是返回一個 View,能夠理解爲視圖。ui
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
接下來咱們再細品SubList
,源碼this
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 這個構造方法中仍是直接引用的父類中的元素,只是單純的將截取的索引從新賦值了一下。spa
使用場景code
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);
}
上面的代碼輸出結果orm
[兔子, 託尼, 啊]
在什麼狀況下會報錯呢?接下來再看個例子,把上面的代碼簡單修改下,讓數據返回 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 異常。
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
方法。 這個方法是有什麼做用呢?
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
源碼中寫的很清楚,判斷原始類型,能夠理解爲父類型原始的 ArrayList 和當前的 SubList 方法中的元素個數作比較,若是不同就報異常。 一、 對 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刪除的:== ==>[兔子, 啊]
二、 對 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 異常,這個異常是對數據比較發現元素被更改過,能夠理解爲髒數據嗎?
一、 SubList 和 ArrayList 之間沒有任何關係
二、千萬不要將 SubList 轉化爲 ArrayList 會報轉換異常
三、對 SubList 視圖元素修改會影響原始父 ArrayList 中的數據。
四、對 ArrayList 數據刪除添加等修改,SubList 會報 Modification 異常
其實咱們能夠理解下,SubList 理解爲一個視圖,其實就是一個內部類,它的實現就是在原始的 ArrayList 中改變了截取的索引位置。
對視圖的操做結果會反映到原始的 ArrayList 中,若是對原始的 ArrayList 作數據的添加刪除操做,很差意思此刻的 SubList 已經報異常了。
通俗一點,能夠修改兒子,不能修改父親。
Lists.newArrayList(subList)
下次見!