2

subList()がsubSet()メソッドとして動作せず、 ConcurrentModificationExceptionをスローするのに subSetがスローされない理由を誰でも説明できますか。どちらのメソッドも Backed Collection を作成したため、おそらくsubList() メソッドの設計者は、変更不可能な元のリストに依存してこのメ​​ソッドを作成しましたが、すべての Backed Collection が同じ動作 ( subSet()など) を持っている場合は良くないでしょうか?

// コード

public class ConcurrentModificationException {
public static void main(String[] args) {
    String[] array = {"Java","Python","Pearl","Ada","Javascript","Go","Clojure"};
    subListEx(array);
    subSetEx(array);
}

private static void subListEx(String[] array) {
    List<String> l = new ArrayList<String>(Arrays.asList(array));
    List<String> l2 = l.subList(2, 4);
    System.out.println(l.getClass().getName());

    // l.add("Ruby"); // ConcurrentModificationException
    // l.remove(2); // ConcurrentModificationException
    l2.remove("Ada"); // OK
    for (String s:l) { System.out.print(s+", "); } 
    System.out.println();
    for (String s:l2) { System.out.print(s+", "); }
}

private static void subSetEx(String[] array) {
    SortedSet<String> s1 = new TreeSet<String>(Arrays.asList(array));
    SortedSet<String> s2 = s1.subSet("Java", "Python");
    s1.remove("Ada");

    for (String s:s1) { System.out.print(s+", "); } 
    System.out.println();
    for (String s:s2) { System.out.print(s+", "); }
}}

前もって感謝します!

4

4 に答える 4

3

動作が文書化されているとおりであることはすでに明らかです。しかし、あなたの主な質問は、 と の動作が異なる理由だと思いArrayListますTreeSet。それは、データが両方のコレクションに内部的に格納される方法に関係しています。

は内部的に配列を使用してデータを格納します。これは、 のサイズが動的に増加するにつれてArrayListサイズが変更されます。ArrayListこれで、指定したリストのを作成するsubListと、指定したインデックスを持つ元のリストが に関連付けられますsubList。したがって、元のリストで行われた構造上の変更 (元の配列のインデックス作成を台無しにする) は、サブリストの一部として格納されているインデックスを無意味にします。ArrayList#subListそのため、メソッドの場合、構造的な変更は許可されません。subList メソッドは、クラス内で名前がinner付けられたクラスのインスタンスを返します。これは次のようになります。SubListArrayList

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には元のリストへの参照が含まれています。そして は、作成parentOffsetしている の開始インデックスに他なりsubListません。オリジナルを変更すると、元のリストlistの値が変更される可能性がありますが、内では変更されません。その場合、クラスと元のリストでは、異なる配列要素を指します。ある時点で、元の配列が十分に短くなり、 に格納されているインデックスが無効になり、 になる可能性もあります。これは確かに望ましくなく、返される subList のセマンティクスはundefinedと見なされ、元のリストに対する構造的な変更が行われます。fromIndexSubListparentOffsetSubListfromIndexSubListOutOfRange

一方、 aTreeSetはそのデータを a に内部的に保存しTreeMapます。にはそのようなインデックスの概念がないためMap、インデックスが分割されるという問題はありません。Aは、キーと値のペアMapのマッピングに他なりません。SubSetの作成には、元の に基づく SubMapの作成が含まれます。オリジナルを変更するには、対応するキーと値のマッピングを無効にするだけでよいため、.MapSetsubMapsubSet

于 2013-08-21T21:07:57.623 に答える
1

の契約List.subList(int, int)がこれをカバーしています。ここに関連する部分があります。

返されたリストはこのリストに基づいているため、返されたリストの非構造的な変更はこのリストに反映され、その逆も同様です。
...
バッキング リスト (つまり、このリスト) が返されたリスト以外の方法で構造的に変更された場合、このメソッドによって返されるリストのセマンティクスは未定義になります。(構造的な変更とは、このリストのサイズを変更したり、進行中の反復によって誤った結果が得られるような方法でリストを混乱させたりするものです。)

サンプルでは、​​バッキング リストに構造的な変更を加えているため、結果は未定義です。

于 2013-08-21T20:52:26.113 に答える
0

ドキュメントは非常に明確です aList.subList()。リストのビューを返します。返されたオブジェクト元のリストに基づいているため、想定する必要があります。

ドキュメントもSetsに関して非常に明確です:

このクラスの iterator メソッドによって返される反復子は、フェイルファストです。反復子の作成後に、反復子自体の remove メソッド以外の方法でセットが変更されると、反復子は ConcurrentModificationException をスローします。

s を使用する方が を使用するよりも問題を作成しListやすいのはなぜSetですか? 要素を削除すると、Setその効果は非常に明確です。要素は、セットとサブセットの両方で、セットに含まれなくなります。挿入操作でも同じです。しかし、 a から要素を削除するListと、サブリストがそれに応じてインデックスを調整する (場合によってはサイズも調整する) ことを意味するのでしょうか?それとも、右または左から要素を追加することでサイズを維持するだけなのでしょうか? そして、2 番目のケースで、バッキング リストが短くなりすぎた場合はどうなるでしょうか。動作が複雑すぎて定義できません。また、実装には制限が多すぎます。

例: リストは [a,b,c,d]、サブリストは list.subList(1,3) ([b,c]) です。 aリストから削除されます。リストの効果は明ら​​かです。しかし、サブリストは [b,c] のまま (範囲を (0,2) に変更) しますか、それとも [c,d] (範囲を (1,3) に維持) しますか?

例: リストは [a,b,c,d]、サブリストは list.subList(1,3) ([b,c]) です。 bリストから削除されます。繰り返しますが、リストの効果は明ら​​かです。しかし、サブリストは [c] (1,2)、[a,c] (0,2)、または [c,d] (1,3) になりますか?

于 2013-08-21T20:53:54.400 に答える
0

両方のJavadocはこれについて非常に明確です:

ArrayList.sublist()から

バッキング リスト (つまり、このリスト) が返されたリスト以外の方法で構造的に変更された場合、このメソッドによって返されるリストのセマンティクスは未定義になります。(構造的な変更とは、このリストのサイズを変更するもの、または進行中の反復が誤った結果をもたらす可能性があるような方法でそれを乱すものです。)

したがって、あなたが を受け取ることが保証されているわけではありませんがConcurrentModificationException、それは問題外ではありません. バッキング リストを変更した場合の動作は未定義です。

一方...

TreeSet.subSet()から

要素が fromElement (これを含む) から toElement (これを含まない) までの範囲にある、このセットの部分のビューを返します。(fromElement と toElement が等しい場合、返されるセットは空です。) 返されるセットはこのセットによってサポートされるため、返されるセットの変更はこのセットに反映され、その逆も同様です。返されたセットは、このセットがサポートするすべてのオプションのセット操作をサポートします。

ここで注意する必要はありません。バッキング セットを変更しても問題はありません。

于 2013-08-21T20:55:18.950 に答える