> Java > java지도 시간 > 본문

Java 개선 구성(37) - Java 컬렉션 세부 정보(3): subList의 결함

黄舟
풀어 주다: 2017-02-11 10:37:00
원래의
1359명이 탐색했습니다.

String 객체를 분할하기 위해 subString 메서드를 자주 사용하는 동시에 subList, subMap 및 subSet을 사용하여 List, Map, 및 Set.processing을 수행하지만 이 분할에는 특정 결함이 있습니다.

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));
    }
로그인 후 복사

이 예는 매우 간단합니다. 생성자와 하위 목록을 통해 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);
    }
로그인 후 복사

, toIndex가 합법적인지 여부. 합법적이면 subList 객체가 직접 반환됩니다. 새 객체를 생성할 때 전달되는 매개변수는 원본 목록을 나타내기 때문에 매우 중요합니다.

/**
     * 继承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;
        }
    }
로그인 후 복사


SubLsit은 ArrayList와 마찬가지로 상속되는 ArrayList의 내부 클래스입니다. AbstractList를 구현하고 RandomAccess 인터페이스를 구현합니다. 또한 get, set, add 및 제거와 같이 일반적으로 사용되는 목록 메서드를 제공합니다. 하지만 생성자에는 약간 특별합니다.

1. this.parent = parent 및 parent는 앞에 전달되는 목록입니다. 즉, this.parent는 원본 목록에 대한 참조입니다.

2. this.offset = 오프셋 + fromIndex; this.parentOffset = fromIndex;. 동시에 생성자에서 modCount(빠른 실패 메커니즘)도 전달합니다.

get 메소드를 다시 살펴보겠습니다. get 메소드에서 return ArrayList.this.elementData(offset + index); 이 코드는 get이 반환하는 것이 오프셋 + 원본 목록 요소의 인덱스 위치입니다. 추가 메소드

parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
로그인 후 복사

및 제거 메소드

E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
로그인 후 복사

에도 동일한 원칙이 적용됩니다. 여기서는 subList가 반환한 SubList도 AbstractList의 하위 클래스임을 판단할 수 있습니다. 동시에 해당 메서드(get, set, add, delete 등)는 모두 원래 목록에서 작동하지 않습니다. 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中文网(www.php.cn)!


원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿