SubList到底怎么轉化為ArrayList?


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&lt;E&gt; 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 方法。 這個方法是有什么作用呢?

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)

下次見!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM