<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么阿里巴巴強(qiáng)制不要在 foreach 里執(zhí)行刪除操作

          共 4378字,需瀏覽 9分鐘

           ·

          2021-10-21 12:37

          那天,小二去阿里面試,面試官老王一上來就甩給了他一道面試題:為什么阿里的 Java 開發(fā)手冊里會強(qiáng)制不要在 foreach 里進(jìn)行元素的刪除操作?小二聽完就面露喜色,因?yàn)閮赡昵?,也就?2021 年,他在《Java 程序員進(jìn)階之路》專欄上的第 63 篇看到過這題??。

          PS:star 這種事,只能求,不求沒效果,鐵子們,《Java 程序員進(jìn)階之路》在 GitHub 上已經(jīng)收獲了 437 枚星標(biāo),小伙伴們趕緊去點(diǎn)點(diǎn)了,沖 500 star!

          https://github.com/itwanger/toBeBetterJavaer


          為了鎮(zhèn)樓,先搬一段英文來解釋一下 fail-fast。

          In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.

          這段話的大致意思就是,fail-fast 是一種通用的系統(tǒng)設(shè)計(jì)思想,一旦檢測到可能會發(fā)生錯誤,就立馬拋出異常,程序?qū)⒉辉偻聢?zhí)行。

          public?void?test(Wanger?wanger)?{???
          ????if?(wanger?==?null)?{
          ????????throw?new?RuntimeException("wanger?不能為空");
          ????}
          ????
          ????System.out.println(wanger.toString());
          }

          一旦檢測到 wanger 為 null,就立馬拋出異常,讓調(diào)用者來決定這種情況下該怎么處理,下一步 wanger.toString() 就不會執(zhí)行了——避免更嚴(yán)重的錯誤出現(xiàn)。

          很多時候,我們會把 fail-fast 歸類為 Java 集合框架的一種錯誤檢測機(jī)制,但其實(shí) fail-fast 并不是 Java 集合框架特有的機(jī)制。

          之所以我們把 fail-fast 放在集合框架篇里介紹,是因?yàn)閱栴}比較容易再現(xiàn)。

          List?list?=?new?ArrayList<>();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");

          for?(String?str?:?list)?{
          ?if?("沉默王二".equals(str))?{
          ??list.remove(str);
          ?}
          }

          System.out.println(list);

          這段代碼看起來沒有任何問題,但運(yùn)行起來就報錯了。

          根據(jù)錯誤的堆棧信息,我們可以定位到 ArrayList 的第 901 行代碼。

          final?void?checkForComodification()?{
          ????if?(modCount?!=?expectedModCount)
          ????????throw?new?ConcurrentModificationException();
          }

          也就是說,remove 的時候觸發(fā)執(zhí)行了 checkForComodification 方法,該方法對 modCount 和 expectedModCount 進(jìn)行了比較,發(fā)現(xiàn)兩者不等,就拋出了 ConcurrentModificationException 異常。

          為什么會執(zhí)行 checkForComodification 方法呢?

          是因?yàn)?for-each 本質(zhì)上是個語法糖,底層是通過迭代器 Iterator 配合 while 循環(huán)實(shí)現(xiàn)的,來看一下反編譯后的字節(jié)碼。

          List?list?=?new?ArrayList();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");
          Iterator?var2?=?list.iterator();

          while(var2.hasNext())?{
          ????String?str?=?(String)var2.next();
          ????if?("沉默王二".equals(str))?{
          ????????list.remove(str);
          ????}
          }

          System.out.println(list);

          來看一下 ArrayList 的 iterator 方法吧:

          public?Iterator?iterator()?{
          ????return?new?Itr();
          }

          內(nèi)部類 Itr 實(shí)現(xiàn)了 Iterator 接口。

          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;

          ????Itr()?{}

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

          ????@SuppressWarnings("unchecked")
          ????public?E?next()?{
          ????????checkForComodification();
          ????????int?i?=?cursor;
          ????????Object[]?elementData?=?ArrayList.this.elementData;
          ????????if?(i?>=?elementData.length)
          ????????????throw?new?ConcurrentModificationException();
          ????????cursor?=?i?+?1;
          ????????return?(E)?elementData[lastRet?=?i];
          ????}
          }

          也就是說 new Itr() 的時候 expectedModCount 被賦值為 modCount,而 modCount 是 List 的一個成員變量,表示集合被修改的次數(shù)。由于 list 此前執(zhí)行了 3 次 add 方法。

          • add 方法調(diào)用 ensureCapacityInternal 方法
          • ensureCapacityInternal 方法調(diào)用 ensureExplicitCapacity 方法
          • ensureExplicitCapacity 方法中會執(zhí)行 modCount++

          所以 modCount 的值在經(jīng)過三次 add 后為 3,于是 new Itr() 后 expectedModCount 的值也為 3。

          執(zhí)行第一次循環(huán)時,發(fā)現(xiàn)“沉默王二”等于 str,于是執(zhí)行 list.remove(str)。

          • remove 方法調(diào)用 fastRemove 方法
          • fastRemove 方法中會執(zhí)行 modCount++
          private?void?fastRemove(int?index)?{
          ????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
          }

          modCount 的值變成了 4。

          執(zhí)行第二次循環(huán)時,會執(zhí)行 Itr 的 next 方法(String str = (String) var3.next();),next 方法就會調(diào)用 checkForComodification 方法,此時 expectedModCount 為 3,modCount 為 4,就只好拋出 ConcurrentModificationException 異常了。

          那其實(shí)在阿里巴巴的 Java 開發(fā)手冊里也提到了,不要在 for-each 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請使用 Iterator 方式。

          那原因其實(shí)就是我們上面分析的這些,出于 fail-fast 保護(hù)機(jī)制。

          那該如何正確地刪除元素呢

          1)remove 后 break

          List?list?=?new?ArrayList<>();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");

          for?(String?str?:?list)?{
          ?if?("沉默王二".equals(str))?{
          ??list.remove(str);
          ??break;
          ?}
          }

          break 后循環(huán)就不再遍歷了,意味著 Iterator 的 next 方法不再執(zhí)行了,也就意味著 checkForComodification 方法不再執(zhí)行了,所以異常也就不會拋出了。

          但是呢,當(dāng) List 中有重復(fù)元素要刪除的時候,break 就不合適了。

          2)for 循環(huán)

          List?list?=?new?ArrayList<>();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");
          for?(int?i?=?0,?n?=?list.size();?i??String?str?=?list.get(i);
          ?if?("沉默王二".equals(str))?{
          ??list.remove(str);
          ?}
          }

          for 循環(huán)雖然可以避開 fail-fast 保護(hù)機(jī)制,也就說 remove 元素后不再拋出異常;但是呢,這段程序在原則上是有問題的。為什么呢?

          第一次循環(huán)的時候,i 為 0,list.size() 為 3,當(dāng)執(zhí)行完 remove 方法后,i 為 1,list.size() 卻變成了 2,因?yàn)?list 的大小在 remove 后發(fā)生了變化,也就意味著“沉默王三”這個元素被跳過了。能明白嗎?

          remove 之前 list.get(1) 為“沉默王三”;但 remove 之后 list.get(1) 變成了“一個文章真特么有趣的程序員”,而 list.get(0) 變成了“沉默王三”。

          3)使用 Iterator

          List?list?=?new?ArrayList<>();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");

          Iterator?itr?=?list.iterator();

          while?(itr.hasNext())?{
          ?String?str?=?itr.next();
          ?if?("沉默王二".equals(str))?{
          ??itr.remove();
          ?}
          }

          為什么使用 Iterator 的 remove 方法就可以避開 fail-fast 保護(hù)機(jī)制呢?看一下 remove 的源碼就明白了。

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

          刪除完會執(zhí)行 expectedModCount = modCount,保證了 expectedModCount 與 modCount 的同步。


          簡單地總結(jié)一下,fail-fast 是一種保護(hù)機(jī)制,可以通過 for-each 循環(huán)刪除集合的元素的方式驗(yàn)證這種保護(hù)機(jī)制。

          那也就是說,for-each 本質(zhì)上是一種語法糖,遍歷集合時很方面,但并不適合拿來操作集合中的元素(增刪)。

          這是《Java 程序員進(jìn)階之路》專欄的第 63 篇。Java 程序員進(jìn)階之路,風(fēng)趣幽默、通俗易懂,對 Java 初學(xué)者極度友好和舒適??,內(nèi)容包括但不限于 Java 語法、Java 集合框架、Java IO、Java 并發(fā)編程、Java 虛擬機(jī)等核心知識點(diǎn)。

          點(diǎn)擊上方名片,發(fā)送消息「03」 就可以獲取《Java 程序員進(jìn)階之路》的 PDF 版了,一起成為更好的 Java 工程師。

          瀏覽 35
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  96精品久久久久久久久久 | 影音先锋女人站 | 免费在线观看黄片网站 | 挨操成人免费视频 | 大香蕉人人 |