ホームページ > Java > &#&チュートリアル > Java 改善構成 (37) - Java コレクションの詳細 (3): subList の欠陥

Java 改善構成 (37) - Java コレクションの詳細 (3): subList の欠陥

黄舟
リリース: 2017-02-11 10:37:00
オリジナル
1372 人が閲覧しました

String オブジェクトをセグメント化するために、subString メソッドをよく使用しますが、同時に、subList、subMap、および subSet を使用して List、Map、Set をセグメント化することもできますが、このセグメント化にはいくつかの欠陥があります。

1. SubList はビューだけを返します

まず、次の例を見てみましょう:

public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<Integer>();
        list1.add(1);
        list1.add(2);
        
        //通过构造函数新建一个包含list1的列表 list2
        List<Integer> list2 = new ArrayList<Integer>(list1);
        
        //通过subList生成一个与list1一样的列表 list3
        List<Integer> list3 = list1.subList(0, list1.size());
        
        //修改list3
        list3.add(3);
        
        System.out.println("list1 == list2:" + list1.equals(list2));
        System.out.println("list1 == list3:" + list1.equals(list3));
    }
ログイン後にコピー

この例は、コンストラクターと subList を通じて list1 と同じリストを再生成するだけです。次に list3 を変更し、最後に list1 == list2?、list1 == list3? を比較します。私たちの従来の考え方によれば、これは次のようになるはずです: list3 は add によって新しい要素を追加するため、list1 と等しくてはなりません。また、list2 は list1 によって構築されるため、等しいはずです。したがって、結果は次のようになります:

list1 == list2:true
list1 == list3: false
ログイン後にコピー

结結果 まず、結果が正しいかどうかに関係なく、まず Sublist のソースコードを見てみましょう:

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }
ログイン後にコピー
E

SublistRangeck メソッドは、Fromindex と Toindex が正当であるかどうかを判断します。パラメータ this が渡されました。これは元のリストを表すため非常に重要です。

/**
     * 继承AbstractList类,实现RandomAccess接口
     */
    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;
        }

        //set方法
        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;
        }

        //get方法
        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

        //add方法
        public void add(int index, E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

        //remove方法
        public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }
    }
ログイン後にコピー


SubLs​​it は ArrayList と同様に AbstractList を継承し、RandomAccess インターフェースを実装します。また、get、set、add、remove などの一般的に使用されるリスト メソッドも提供します。ただし、そのコンストラクターは少し特殊です。コンストラクター内で注意すべき点が 2 つあります:

1. this.parent =parent、parent は前に渡されるリスト、つまり this.parent が元のリストです。参照。

2. this.offset = offset + fromIndex; this.parentOffset = fromIndex;。同時に、コンストラクターで modCount (フェイルファスト メカニズム) も渡します。

get メソッドをもう一度見てみましょう。 get メソッドでは、 return ArrayList.this.elementData(offset +index); このコードは、get によって返される要素が、オフセット + インデックス位置にある要素であることを明確に示しています。元のリスト。同じ原理が add メソッドにも存在します:

parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
ログイン後にコピー

E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
ログイン後にコピー

削除メソッド内get、set、add、remove などはすべて元のリストに対して動作します。subString のような新しいオブジェクトは生成されません。したがって、subList は元のリストのビューのみを返し、そのすべての操作は最終的に元のリストに適用されます。したがって、ここから、上記の結果は上記の答えの正反対であることがわかります:

list1 == list2:false
list1 == list3:true
ログイン後にコピー

Java细节(3.1):subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上

二、subList生成子列表后,不要试图去操作原列表

从上面我们知道subList生成的子列表只是原列表的一个视图而已,如果我们操作子列表它产生的作用都会在原列表上面表现,但是如果我们操作原列表会产生什么情况呢?

public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<Integer>();
        list1.add(1);
        list1.add(2);
        
        //通过subList生成一个与list1一样的列表 list3
        List<Integer> list3 = list1.subList(0, list1.size());
        //修改list3
        list1.add(3);
        
        System.out.println("list1&#39;size:" + list1.size());
        System.out.println("list3&#39;size:" + list3.size());
    }
ログイン後にコピー


该实例如果不产生意外,那么他们两个list的大小都应该都是3,但是偏偏事与愿违,事实上我们得到的结果是这样的:

list1&#39;size:3
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(Unknown Source)
    at java.util.ArrayList$SubList.size(Unknown Source)
    at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)
ログイン後にコピー


list1正常输出,但是list3就抛出ConcurrentModificationException异常,看过我另一篇博客的同仁肯定对这个异常非常,fail-fast?不错就是fail-fast机制,在fail-fast机制中,LZ花了很多力气来讲述这个异常,所以这里LZ就不对这个异常多讲了(更多请点这里:Java提高篇(三四)—–fail-fast机制)。我们再看size方法:

public int size() {
            checkForComodification();
            return this.size;
        }
ログイン後にコピー


size方法首先会通过checkForComodification验证,然后再返回this.size。

private void checkForComodification() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }
ログイン後にコピー

该方法表明当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException。同时我们知道modCount 在new的过程中 “继承”了原列表modCount,只有在修改该列表(子列表)时才会修改该值(先表现在原列表后作用于子列表)。而在该实例中我们是操作原列表,原列表的modCount当然不会反应在子列表的modCount上啦,所以才会抛出该异常。

对于子列表视图,它是动态生成的,生成之后就不要操作原列表了,否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态,要操作就操作子列表:

//通过subList生成一个与list1一样的列表 list3
List<Integer> list3 = list1.subList(0, list1.size());
        
//对list1设置为只读状态
list1 = Collections.unmodifiableList(list1);
ログイン後にコピー

Java细节(3.2):生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常

三、推荐使用subList处理局部列表

在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据,可能我们会这样处理:

for(int i = 0 ; i < list1.size() ; i++){
   if(i >= 100 && i <= 200){
       list1.remove(i);
       /*
        * 当然这段代码存在问题,list remove之后后面的元素会填充上来,
         * 所以需要对i进行简单的处理,当然这个不是这里讨论的问题。
         */
   }
}
ログイン後にコピー

这个应该是我们大部分人的处理方式吧,其实还有更好的方法,利用subList。在前面LZ已经讲过,子列表的操作都会反映在原列表上。所以下面一行代码全部搞定:

list1.subList(100, 200).clear();
ログイン後にコピー

简单而不失华丽!!!!!

以上就是Java提高配(三七)—–Java集合细节(三):subList的缺陷的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!


ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート