SubList到底怎么轉(zhuǎn)化為ArrayList?

SubList

大家好,今天 Tony 給大家講個SubList轉(zhuǎn)化的坑。

這個錯誤真的會被忽略,大家好好的看看,這個錯誤我們生產(chǎn)環(huán)境還真的遇到過。

集合類型相信大家都很熟悉,在 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);
}

接下來我們再細(xì)品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 中的一個內(nèi)部類,繼承了 AbstractList,實(shí)現(xiàn)了 RandomAccess,從上面的代碼中可以`看到,在 SubList 這個構(gòu)造方法中還是直接引用的父類中的元素,只是單純的將截取的索引重新賦值了一下。

使用場景

    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);
}

上面的代碼輸出結(jié)果

[兔子, 托尼, 啊]

在什么情況下會報錯呢?接下來再看個例子,把上面的代碼簡單修改下,讓數(shù)據(jù)返回 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

為什么不能直接轉(zhuǎn)換為 ArrayList 呢?上面的源碼已經(jīng)顯示了,SubList 只是一個內(nèi)部類,它繼承 AbstractList 和 ArrayList 根本都沒有關(guān)系,所以直接轉(zhuǎn)化會報 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 和當(dāng)前的 SubList 方法中的元素個數(shù)做比較,如果不一樣就報異常。
1、 對 subList 視圖做數(shù)據(jù)的刪除

    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);
    }

上面的代碼運(yùn)行正常輸出結(jié)果

namesList原始的:== ==>[兔子, 托尼, 啊]
subList截取的:== ==>[兔子, 托尼]
subList刪除的:== ==>[兔子]
namesList刪除的:== ==>[兔子, 啊]

2、 對 ArrayList 做數(shù)據(jù)的刪除

 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);
}

輸出結(jié)果報異常了

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)

當(dāng)我們對父元素 ArrayList 中對數(shù)據(jù)進(jìn)行刪除操作的時候,我們會發(fā)現(xiàn) SubList 會報一個
ConcurrentModificationException 異常,這個異常是對數(shù)據(jù)比較發(fā)現(xiàn)元素被更改過,可以理解為臟數(shù)據(jù)嗎?

總結(jié)

1、 SubList 和 ArrayList 之間沒有任何關(guān)系

2、千萬不要將 SubList 轉(zhuǎn)化為 ArrayList 會報轉(zhuǎn)換異常

3、對 SubList 視圖元素修改會影響原始父 ArrayList 中的數(shù)據(jù)。

4、對 ArrayList 數(shù)據(jù)刪除添加等修改,SubList 會報 Modification 異常

其實(shí)我們可以理解下,SubList 理解為一個視圖,其實(shí)就是一個內(nèi)部類,它的實(shí)現(xiàn)就是在原始的 ArrayList 中改變了截取的索引位置。

對視圖的操作結(jié)果會反映到原始的 ArrayList 中,如果對原始的 ArrayList 做數(shù)據(jù)的添加刪除操作,不好意思此刻的 SubList 已經(jīng)報異常了。

通俗一點(diǎn),可以修改兒子,不能修改父親。

結(jié)果

SubList 轉(zhuǎn)化為 ArrayList 可以用 Guava 中的封裝方法

Lists.newArrayList(subList)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容