2017-09-27 5 views
3

-1を削除してArrayListを空にするとConcurrentModificationExceptionを投げ、同じ空から0を取り除くとArrayListNoSuchElementExceptionを投げます。ArrayList remove()の特有の動作 - なぜですか?

以下のコード見つけてください:私の理解NoSuchElementExceptionから

public class Test { 
    public static void main(String[] argv) { 

     ArrayList<Integer> list = new ArrayList<Integer>(); 
     Iterator<Integer> it = list.iterator(); 
     try { 
      list.remove(-1); 
     } catch (IndexOutOfBoundsException e) { 

     } 
     try { 
      it.next();// Throwing ConcurrentModificationException 
     } catch (ConcurrentModificationException e) { 
      System.err.println("ConcurrentModificationException 1"); 
     } catch (NoSuchElementException e) { 
      System.err.println("NoSuchElementException 1 "); 
     } 

     list = new ArrayList<Integer>(); 
     it = list.iterator(); 
     try { 
      list.remove(0); 
     } catch (IndexOutOfBoundsException e) { 
     } 
     try { 
      it.next();// Throwing NoSuchElementException 
     } catch (NoSuchElementException e) { 
      System.err.println("NoSuchElementException 2"); 
     } catch (ConcurrentModificationException e) { 
      System.err.println("ConcurrentModificationException 2 "); 
     } 

    } 
} 

は結構ですが、なぜConcurrentModificationExceptionがスローされますか?

+0

これはイテレータの動作です。リストを変更すると、リストが呼び出されます。イテレータはCMEをスローします。私が疑う呼び出しの違いは、 'list.remove(0)'と 'list.remove(-1)'とで何が起こるかということです。 – matt

答えて

4

ArrayListのコードをチェックすると、最初にレンジチェックが実行され、次に変更カウントが追加されます。

範囲チェック方法では、範囲チェックは正の数のみです。

if (index >= size) 
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 

だからremove(0)はMODのカウントを追加しませんが、remove(-1)はありません。 modCountにより、イテレータはConcurrentModificationExceptionをスローします。

+0

しかし、なぜこのように実装されていますか? – algrid

+0

[grepcode]の@MiljenMikic(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/ArrayList.java#ArrayList.rangeCheck%28int% 29)バージョン8u40の場合、それはまだremove(int)の正の範囲チェックです。 – matt

+0

@algridどの実装を参照していますか?反復子が並行変更例外をスローする、または範囲チェックが正の整数のみであるという事実? – matt

3

非常に小さなものですが、(IMO)バグです。

コードが(Javaの8で)次のようになります。

public E remove(int index) { 
    rangeCheck(index); 
    modCount++; 
    E oldValue = elementData(index); 

rangeCheckコールチェックindex < sizeものではなくindex >= 0。おそらく、それは冗長チェックを避けるためです... elementData呼び出しが、負のインデックスの場合を扱う最適化不可能な配列境界チェックを行うためです。

しかし、キャッチは、modCount++が配列の境界チェックの前に起こっていることです。おっと!

の呼び出しの後で、elementData呼び出しの後に移動する可能性がありますが、これは正しくない可能性があります。その結果、modCountvolatileであるため、読み取り/書き込みには次に発生するelementDataへのアクセスに暗黙の同期効果があります。

ご報告いただいた場合に修正されますか?おそらくそうではありません!

  • 上記の方法でコードを実行すると、「動作します」というコードが破損する可能性があります。確かにあなたが破っているコードは、volatileのために予期せぬ同期に頼っているため、間違っています。それでも、コードが壊れた人はという悲鳴があります。あなたが明示的にsize >= 0チェックを追加した場合(なく、少なくともこれは診断が困難切れになるので。)

  • あるいは、あなたが最も可能性の高い作業コードが測定可能遅くします。

  • "バグ"は、とにかく不正なコードにのみ影響します。反復と同時にArrayList::removeを呼び出すべきではありません。

関連する問題