欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解決在for循環(huán)中remove list報錯越界的問題

 更新時間:2020年12月03日 09:34:07   作者:trebleZ  
這篇文章主要介紹了解決在for循環(huán)中remove list報錯越界的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

最近在搞一個購物車的功能,里面有一個批量刪除的操作,采用的是ExpandableListView以及BaseExpandableListAdapter。視乎跟本篇無關(guān)緊要,主要是為了記錄一個java基礎(chǔ)。迭代器iterator的使用

一、錯誤代碼(主要就是購物車的批量刪除)

/**
   * 刪除選中的
   */
  public void delSelect() {
    int groupSize;
    if (mGropBeens != null) {
      groupSize = mGropBeens.size();
    } else {
      return;
    }
    for (int i = 0; i < groupSize; i++) {
      int childSize = mGropBeens.get(i).getChildBean().size();
      for (int j = 0; j < childSize; j++) {
        if (mGropBeens.get(i).getChildBean().get(j).isChecked()) {
          DataSupport.deleteAll(ShopcartBean.class, "product_id=?", mGropBeens.get(i).getChildBean().get(j).getProduct_id());
        mGropBeens.get(i).getChildBean().remove(j);
        }
      }
    }
    notifyDataSetChanged();
  }

分析一、其實就是一個循環(huán)遍歷list進(jìn)行remove的操作,后來我分析了一下,錯誤的很明顯,就是你remove了這個list以后,list的長度就改變了,然后你繼續(xù)遍歷,就會報錯。感覺躲不了啊。錯誤有了,我覺得無法下手,后面既然remove不了,我就先刪除本地數(shù)據(jù)庫的方式,然后遍歷對data進(jìn)行賦值。。。躲一下

/**
   * 刪除選中的
   */
  public void delSelect() {
    int groupSize;
    if (mGropBeens != null) {
      groupSize = mGropBeens.size();
    } else {
      return;
    }
    for (int i = 0; i < groupSize; i++) {
      int childSize = mGropBeens.get(i).getChildBean().size();
      for (int j = 0; j < childSize; j++) {
        if (mGropBeens.get(i).getChildBean().get(j).isChecked()) {
          DataSupport.deleteAll(ShopcartBean.class, "product_id=?", mGropBeens.get(i).getChildBean().get(j).getProduct_id());
//          mGropBeens.get(i).getChildBean().remove(j);
        }
      }
    }
    //刷新數(shù)據(jù)源
    for (int i = 0; i < mGropBeens.size(); i++) {
      mGropBeens.get(i).getChildBean().clear();
      List<ShopcartBean> shopcartBeanlists = DataSupport.where("top_category_id=?", mGropBeens.get(i).getGroupId()).find(ShopcartBean.class);
      mGropBeens.get(i).setChildBean(shopcartBeanlists);
    }
    notifyDataSetChanged();
  }

分析二、寫了這樣以后還是感覺很不爽啊。明明我都循環(huán)了一遍知道要刪除這個對象了,還要等遍歷完,僅僅改變它的適配器的data。感覺不爽,隨意的百度了下,發(fā)現(xiàn)有專門的解決方案,就是迭代器iterator

二、爭取的打開方式

 /**
   * 刪除選中的
   */
  public void delSelect() {
    int groupSize;
    if (mGropBeens != null) {
      groupSize = mGropBeens.size();
    } else {
      return;
    }
    for (int i = 0; i < groupSize; i++) {
      Iterator<ShopcartBean> iterator = mGropBeens.get(i).getChildBean().iterator();
      while (iterator.hasNext()) {
        ShopcartBean shopcartBean = iterator.next();
        if (shopcartBean.isChecked()) {
          DataSupport.deleteAll(ShopcartBean.class, "product_id=?", shopcartBean.getProduct_id());
          iterator.remove();
        }
      }
    }
    notifyDataSetChanged();
  }

分析三、發(fā)現(xiàn)這個玩意感覺還是很驚喜的,同時也感嘆自己基礎(chǔ)薄弱了。

一般循環(huán)for和foreach都需要先知道集合的類型,甚至是集合內(nèi)元素的類型,即需要訪問內(nèi)部的成員;iterator是一個接口類型,他不關(guān)心集合或者數(shù)組的類型,而且他還能隨時修改和刪除集合的元素。他不包含任何有關(guān)他所遍歷的序列的類型信息,能夠?qū)⒈闅v序列的操作與序列底層的 結(jié)構(gòu)分離。

基本使用模式就是:

 List<object> arr=xxx;//把你的list賦值過來
 Iterator it = arr.iterator();
  while(it.hasNext()){ 
  object o =it.next();//當(dāng)前遍歷對象
  iterator.remove();//刪除或修改等等
  }

三、ok,先做記錄,后面繼續(xù)深入。have a nice day.這就是在一個list中刪除多個元素的正確解法。

補(bǔ)充知識:詳解ArrayList在遍歷時remove元素所發(fā)生的并發(fā)修改異常的原因及解決方法

本文將以“在遍歷中刪除”為著手點,在其基礎(chǔ)上進(jìn)行源碼分析及相關(guān)問題解決。modCount的含義、迭代器所包含的方法、為什么會發(fā)生并發(fā)修改異常都將會在這篇文章中進(jìn)行說明。

引入

這是一個并發(fā)修改異常的示例,它使用了迭代器iterator來獲取元素,同時使用ArrayList自身的remove方法移除元素(使用增強(qiáng)for循環(huán)去遍歷獲取元素亦會如此,增強(qiáng)for循環(huán)底層用的也是迭代器,enhanced for loop is nothing but a syntactic sugar over Iterator in Java)

public static void main(String[] args) {
 //請動手實踐運行一下
  List<String> list = new ArrayList<String>();
  list.add("a");
  list.add("b");
  list.add("c");
  list.add("d");
  list.add("e");
  Iterator<String> iterator = list.iterator();
  while (iterator.hasNext()) {
    String str = iterator.next();
    if (str.equals("a")) {
      list.remove(str);
    } else {
      System.out.println(str);
    }
  }
}

原因分析

ArrayList內(nèi)部實現(xiàn)了迭代器Itr,如圖所示

通過迭代器獲取元素時(iterator.next())會進(jìn)行checkForComodification,源碼如下

public E next() {
  checkForComodification();/***看這行***/
  int i = cursor;
  if (i >= size)
    throw new NoSuchElementException();
  Object[] elementData = ArrayList.this.elementData;
  if (i >= elementData.length)
    throw new ConcurrentModificationException();
  cursor = i + 1;//cursor向后挪一位
  return (E) elementData[lastRet = i];//lastRet為當(dāng)前取出的元素所在索引,后面會用到
}
final void checkForComodification() {/***再看這里***/
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

modCount即此列表已被結(jié)構(gòu)修改的次數(shù)。 結(jié)構(gòu)修改是改變列表大小的那些修改(如增刪,注意列表大小是size而不是capacity),或以其他方式擾亂它,使得正在進(jìn)行的迭代可能產(chǎn)生不正確的結(jié)果的那些修改。

而expectedModCount會在迭代器被創(chuàng)建出來時初始化為modCount,源碼如下

private class Itr implements Iterator<E> {
  int cursor;    // index of next element to return
  int lastRet = -1; // index of last element returned; -1 if no such
  int expectedModCount = modCount;
  
  //instance methods...
}  

是不是發(fā)現(xiàn)什么端倪了呢?當(dāng)調(diào)用remove時(進(jìn)而調(diào)用fastRemove)即被視為結(jié)構(gòu)修改,因此modCount的值是會發(fā)生變化的,這樣當(dāng)程序再次通過iterator.next()獲取元素時,通過checkForComodification方法發(fā)現(xiàn)modCount變化了,而expectedModCount 依然是初始化時的值,因此拋出ConcurrentModificationException。

讓我們來確認(rèn)一下我們的想法,remove方法及fastRemove方法的源碼如下

public boolean remove(Object o) {
  if (o == null) {
    for (int index = 0; index < size; index++)
      if (elementData[index] == null) {
        fastRemove(index);/***看這行調(diào)用了fastRemove***/
        return true;
      }
  } else {
    for (int index = 0; index < size; index++)
      if (o.equals(elementData[index])) {
        fastRemove(index);
        return true;
      }
  }
  return false;
}

private void fastRemove(int index) {
  modCount++;/***再看這行modCount變化了(自增)***/
  int numMoved = size - index - 1;
  if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
  elementData[--size] = null; // clear to let GC do its work
}

這樣我們就對ArrayList在遍歷時remove元素所發(fā)生的并發(fā)修改異常有了一個明確的了解。

迭代器Itr的補(bǔ)充說明:size是數(shù)組大小,cursor是下一元素的索引(雖然是下一元素的索引,但數(shù)組開始索引是從0開始的,所以cursor默認(rèn)初始化為0)數(shù)組的最大索引一定是小于size的(size-1)索引=size還要取元素的話將會越界。

在說明“如何在不發(fā)生異常的情況下刪除數(shù)據(jù)”前,先說一下根據(jù)上述示例可能會產(chǎn)生的其他問題(如不感興趣也可跳過0.0)。

刪除不規(guī)范時所產(chǎn)生的其他問題

問題1.雖然remove的不規(guī)范,但是程序依然能夠運行,雖然不符合預(yù)期,但是沒有發(fā)生并發(fā)修改異常

如果我們刪除的不是a,而是d的話(最大為e),將會輸出a b c而不會發(fā)生并發(fā)修改異常,代碼如下

while (iterator.hasNext()) {
  String str = (String) iterator.next();
  if (str.equals("d")) {//將原先的a改為d
    list.remove(str);
  } else {
    System.out.println(str);
  }
}

原因分析:因為刪除d時cursor由3變?yōu)?(從0起算),size由5變?yōu)?。因此hasNext返回true,并且循環(huán)結(jié)束,因此不會輸出e(循環(huán)結(jié)束也意味著不會通過next進(jìn)行checkForComodification,所以不會引發(fā)異常)

問題2.看似不會發(fā)生并發(fā)修改異常,可實際卻發(fā)生了0.0

如果我們將要刪除的元素改為e,那么當(dāng)刪除e時cursor由4變?yōu)?,size由5變?yōu)?,5明明大于4了應(yīng)該不會有下一元素了,不會進(jìn)入循環(huán)通過next取元素了,可當(dāng)這么想著的時候,異常卻發(fā)生了,代碼如下:

while (iterator.hasNext()) {
  String str = (String) iterator.next();
  if (str.equals("e")) {
    list.remove(str);
  } else {
    System.out.println(str);
  }
}

原因分析:這是由于iterator.hasNext()的原理導(dǎo)致,點擊hasNext()查看源碼可發(fā)現(xiàn),hasNext并不是由cursor < size來實現(xiàn)的而是通過cursor != size來實現(xiàn)的,這樣程序?qū)⒃俅芜M(jìn)入循環(huán)取元素進(jìn)而發(fā)生并發(fā)修改異常

public boolean hasNext() {
  return cursor != size;
}

如何在不報錯的情況下將元素刪除?

1.通過iterator獲取元素的同時使用iterator的remove方法移除元素,代碼如下

while (iterator.hasNext()) {
  String str = (String) iterator.next();
  if (str.equals("a")) {
    iterator.remove();
  } else {
    System.out.println(str);
  }
}

通過Itr的remove源碼可以發(fā)現(xiàn)(如下),它在每次刪除的同時還會更新expectedModCount為當(dāng)前自增后的modCount,使得下次通過iterator.next()取元素時經(jīng)得住checkForComodification校驗(試想一下如果沒有checkForComodification的話,程序?qū)⒗^續(xù)循環(huán)下去,cursor本指向的是當(dāng)前元素索引的下一位,但remove后數(shù)據(jù)將整體向前竄一位,從而導(dǎo)致cursor指向的索引位置對應(yīng)的數(shù)據(jù)發(fā)生了偏差,上述問題2的情況時若沒有進(jìn)行checkForComodification則還會發(fā)生NoSuchElementException異常,詳見上述next源碼)。

lastRet的值為最新一次通過next獲取元素時,那個元素所對應(yīng)的索引,這里通過將cursor = lastRet,從而把cursor的索引向前移動了一位,繼而避免了取數(shù)據(jù)時的偏差(cursor 與 lastRet的關(guān)系詳見上面的next源碼)

在這里lastRet 會歸為-1(它所對應(yīng)的元素已經(jīng)被刪除了),這也是為什么不能連續(xù)調(diào)用兩次迭代器的remove方法的原因,若執(zhí)意如此,該方法將會拋出IllegalStateException的異常

public void remove() {
  if (lastRet < 0)
    throw new IllegalStateException();
  checkForComodification();

  try {
    ArrayList.this.remove(lastRet);
    cursor = lastRet;
    lastRet = -1;
    expectedModCount = modCount;
  } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
  }
}

2.通過list自身的get方法獲取元素的同時通過自身的remove方法移除元素,代碼如下

for (int i = 0;i<list.size();i++){
  String s = list.get(i);
  if ("a".equals(s)){
    list.remove(i);//進(jìn)而調(diào)用fastRemove
    i--;//相當(dāng)于cursor = lastRet;將返回的下一元素的索引 = 返回的最新元素的索引(當(dāng)前元素索引)
  }else {
    System.out.println(s);
  }
}

該種情況下不會將expectedModCount修正為最新的modCount,同時也不會進(jìn)行checkForComodification的檢查,若此時刪除并不修正當(dāng)前索引的話,將會造成上述的數(shù)據(jù)偏差(遍歷條件中的list.size()保存為固定值或連續(xù)調(diào)用list.remove(i)次數(shù)過多還可以發(fā)生索引越界異常0.0)

注意,不能保證迭代器的快速失敗行為,因為通常來說,在存在不同步的并發(fā)修改的情況下,不可能做出任何嚴(yán)格的保證??焖偈〉牡鲿M最大努力拋出ConcurrentModificationException。因此,編寫依賴于此異常的程序的正確性是錯誤的:迭代器的快速失敗行為應(yīng)僅用于檢測錯誤。

add請參照remove的方式去查閱ArrayList中的內(nèi)部類ListItr

以上這篇解決在for循環(huán)中remove list報錯越界的問題就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 詳解Spring Aop實例之AspectJ注解配置

    詳解Spring Aop實例之AspectJ注解配置

    本篇文章主要介紹了詳解Spring Aop實例之AspectJ注解配置,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • 關(guān)于SpringBoot整合Canal數(shù)據(jù)同步的問題

    關(guān)于SpringBoot整合Canal數(shù)據(jù)同步的問題

    大家都知道canal是阿里巴巴旗下的一款開源工具,純java開發(fā),支持mysql數(shù)據(jù)庫,本文給大家介紹SpringBoot整合Canal數(shù)據(jù)同步的問題,需要的朋友可以參考下
    2022-03-03
  • SpringBoot配置線程池的實現(xiàn)示例

    SpringBoot配置線程池的實現(xiàn)示例

    本文主要介紹了SpringBoot配置線程池的實現(xiàn)示例,主要包括在Spring Boot中創(chuàng)建和配置線程池,包括設(shè)置線程池的大小、隊列容量、線程名稱等參數(shù),感興趣的可以了解一下
    2023-09-09
  • Linux下用java -jar運行可執(zhí)行jar包的方法教程

    Linux下用java -jar運行可執(zhí)行jar包的方法教程

    這篇文章主要給大家介紹了在Linux下用java -jar運行可執(zhí)行jar包的方法教程,文中介紹的非常詳細(xì),相信對大家的工作或者學(xué)習(xí)具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。
    2017-05-05
  • java中throws實例用法詳解

    java中throws實例用法詳解

    在本篇文章里小編給大家分享了一篇關(guān)于java中throws實例用法詳解,有興趣的朋友們可以參考學(xué)習(xí)下。
    2021-01-01
  • 使用Feign?logging?開啟調(diào)用日志

    使用Feign?logging?開啟調(diào)用日志

    這篇文章主要介紹了使用Feign?logging?開啟調(diào)用日志,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Java反射 Field類的使用全方位解析

    Java反射 Field類的使用全方位解析

    這篇文章主要介紹了Java反射 Field類的使用全方位解析,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • springcloud feign調(diào)其他微服務(wù)時參數(shù)是對象的問題

    springcloud feign調(diào)其他微服務(wù)時參數(shù)是對象的問題

    這篇文章主要介紹了springcloud feign調(diào)其他微服務(wù)時參數(shù)是對象的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java中處理stream.filter()的實例代碼

    java中處理stream.filter()的實例代碼

    stream()是Java 8中的一個函數(shù)式接口,用于處理數(shù)據(jù)流,它可以從一個數(shù)據(jù)源,如集合,數(shù)組等生成一個流,這篇文章主要給大家介紹了關(guān)于java中處理stream.filter()的相關(guān)資料,需要的朋友可以參考下
    2024-08-08
  • php上傳文件分類實例代碼

    php上傳文件分類實例代碼

    這篇文章主要介紹了php上傳文件分類實例代碼,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-02-02

最新評論