Heim  >  Artikel  >  Java  >  Java-Verbesserungskonfiguration (37) – Details zur Java-Sammlung (3): Mängel der Unterliste

Java-Verbesserungskonfiguration (37) – Details zur Java-Sammlung (3): Mängel der Unterliste

黄舟
黄舟Original
2017-02-11 10:37:001356Durchsuche

Wir verwenden oft die subString-Methode, um String-Objekte aufzuteilen. Gleichzeitig können wir auch subList, subMap und subSet verwenden, um List, Map, aufzuteilen. und Set.-Verarbeitung, aber diese Segmentierung weist gewisse Mängel auf.

1. subList gibt nur eine Ansicht zurück

Schauen wir uns zunächst das folgende Beispiel an:

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

Dies Beispiel: Es ist nichts weiter, als eine Liste wie Liste1 über den Konstruktor und die Unterliste neu zu generieren, dann Liste3 zu ändern und schließlich Liste1 == Liste2?, Liste1 == Liste3? zu vergleichen. Nach unserem herkömmlichen Denken sollte es so sein: Da Liste3 durch Hinzufügen ein neues Element hinzufügt, darf es nicht gleich Liste1 sein, und Liste2 wird durch Liste1 erstellt, also sollte es gleich sein, also sollte das Ergebnis sein:

list1 == list2:true
list1 == list3: false

Unabhängig davon, ob das Ergebnis korrekt ist oder nicht, schauen wir uns zunächst den Quellcode von subList an:

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

subListRangeCheck Die Methode dient dazu, zu bestimmen, ob fromIndex und toIndex zulässig sind. Wenn sie zulässig sind, wird beim Generieren des neuen Objekts ein Parameter übergeben wichtig, da es die Originalliste darstellt.


/**
     * 继承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;
        }
    }


Die SubLsit ist die innere Klasse von ArrayList ArrayList. Es erbt AbstractList und implementiert die RandomAccess-Schnittstelle. Es bietet auch häufig verwendete Listenmethoden wie Get, Set, Add und Remove. Aber sein Konstruktor ist etwas Besonderes. Es gibt zwei Stellen im Konstruktor, auf die man achten sollte:

1. this.parent = parent ist die vorangestellte Liste, In Mit anderen Worten, this.parent ist ein Verweis auf die ursprüngliche Liste.

2. this.offset = offset + fromIndex; this.parentOffset = fromIndex;. Gleichzeitig wird im Konstruktor sogar modCount (Fail-Fast-Mechanismus) übergeben.

Schauen wir uns noch einmal die get-Methode an: return ArrayList.this.elementData(offset + index); Dieser Code kann deutlich zeigen, dass get der Offset + ist Indexposition der ursprünglichen Listenelemente. Das gleiche Prinzip gilt für die Add-Methode:

parent.add(parentOffset + index, e);
this.modCount = parent.modCount;

und die Remove-Methode

E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;

Das stimmt An diesem Punkt können wir beurteilen, dass die von subList zurückgegebene SubList auch eine Unterklasse von AbstractList ist. Gleichzeitig werden ihre Methoden wie get, set, add, delete usw. auf die ursprüngliche Liste angewendet neues Objekt wie subString. subList gibt also nur eine Ansicht der ursprünglichen Liste zurück und alle seine Operationen werden schließlich auf die ursprüngliche Liste angewendet.

Dann können wir aus der Analyse hier schließen, dass das obige Ergebnis genau das Gegenteil unserer obigen Antwort sein sollte:

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)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn