Java_集合操作_关于subList,子列表

来源:互联网 发布:蓝牙模块设置软件 编辑:程序博客网 时间:2024/05/24 06:33

子列表只是原列表的一个视图

List接口提供了subList方法,其作用是返回一个列表的子列表,这与String类的subString有点类似,但它们的功能是否相同呢?我们来看如下代码:

package deep;import java.util.ArrayList;import java.util.List;public class Client {    public static void main(String[] args) {        // 定义一个包含两个字符串的列表        List<String> c = new ArrayList<String>();        c.add("A");        c.add("B");        // 构造一个包含c列表的字符串列表        List<String> c1 = new ArrayList<String>(c);        // subList生成与c相同的列表        List<String> c2 = c.subList(0, c.size());        // c2增加一个元素        c2.add("C");        System.out.println("c == c1? " + c.equals(c1));        System.out.println("c == c2? " + c.equals(c2));        System.out.println(c);    }}

运行结果:
c == c1? false
c == c2? true
[A, B, C]

c2是通过subList方法从c列表中生成的一个子列表,然后c2又增加了一个元素,可为什么增加了一个元素还相等呢?我们来看subList的源码:

    public List<E> subList(int fromIndex, int toIndex) {        return new SubList<>(this, fromIndex, toIndex);    }
class SubList<E> extends AbstractList<E> {    //原始列表    private final AbstractList<E> l;    //偏移量    private final int offset;    private int size;    //构造函数,注意list参数就是我们的原始列表    SubList(AbstractList<E> list, int fromIndex, int toIndex) {        if (fromIndex < 0)            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);        if (toIndex > list.size())            throw new IndexOutOfBoundsException("toIndex = " + toIndex);        if (fromIndex > toIndex)            throw new IllegalArgumentException("fromIndex(" + fromIndex +                                               ") > toIndex(" + toIndex + ")");        //传递原始列表                                               l = list;        offset = fromIndex;        //子列表的长度        size = toIndex - fromIndex;        this.modCount = l.modCount;    }    public E set(int index, E element) {        rangeCheck(index);        checkForComodification();        return l.set(index+offset, element);    }    public E get(int index) {        rangeCheck(index);        checkForComodification();        return l.get(index+offset);    }    public int size() {        checkForComodification();        return size;    }    public void add(int index, E element) {        rangeCheckForAdd(index);        checkForComodification();        l.add(index+offset, element);        this.modCount = l.modCount;        size++;    }    public E remove(int index) {        rangeCheck(index);        checkForComodification();        E result = l.remove(index+offset);        this.modCount = l.modCount;        size--;        return result;    }    protected void removeRange(int fromIndex, int toIndex) {        checkForComodification();        l.removeRange(fromIndex+offset, toIndex+offset);        this.modCount = l.modCount;        size -= (toIndex-fromIndex);    }    public boolean addAll(Collection<? extends E> c) {        return addAll(size, c);    }    public boolean addAll(int index, Collection<? extends E> c) {        rangeCheckForAdd(index);        int cSize = c.size();        if (cSize==0)            return false;        checkForComodification();        l.addAll(offset+index, c);        this.modCount = l.modCount;        size += cSize;        return true;    }    public Iterator<E> iterator() {        return listIterator();    }    public ListIterator<E> listIterator(final int index) {        checkForComodification();        rangeCheckForAdd(index);        return new ListIterator<E>() {            private final ListIterator<E> i = l.listIterator(index+offset);            public boolean hasNext() {                return nextIndex() < size;            }            public E next() {                if (hasNext())                    return i.next();                else                    throw new NoSuchElementException();            }            public boolean hasPrevious() {                return previousIndex() >= 0;            }            public E previous() {                if (hasPrevious())                    return i.previous();                else                    throw new NoSuchElementException();            }            public int nextIndex() {                return i.nextIndex() - offset;            }            public int previousIndex() {                return i.previousIndex() - offset;            }            public void remove() {                i.remove();                SubList.this.modCount = l.modCount;                size--;            }            public void set(E e) {                i.set(e);            }            public void add(E e) {                i.add(e);                SubList.this.modCount = l.modCount;                size++;            }        };    }

通过阅读这段代码,我们就非常清楚subList方法的实现原理了;它返回的SubList类也是AbstractList的子类,其所有的方法如get、set、add、remove等都是在原始列表上的操作,它自身并没有生成一个数组或是链表,也就是子列表自是原列表的一个视图(View),所有的修改动作都反应在了原列表上。所以最后打印的c为[A, B, C]。

推荐使用subList处理局部列表

我们来看这样一个简单的需求:一个列表有20个元素,现在要删除索引位置为6-10的元素。代码如下:

package deep;import java.util.ArrayList;import java.util.List;public class Client {    public static void main(String[] args) {        List<Integer> list = new ArrayList<Integer>(20);        list.add(0);        list.add(1);        list.add(2);        list.add(3);        list.add(4);        list.add(5);        list.add(6);        list.add(7);        list.add(8);        list.add(9);        list.add(10);        list.add(11);        list.add(12);        list.add(13);        list.add(14);        list.add(15);        list.add(16);        list.add(17);        list.add(18);        list.add(19);        int offset = 0;        for (int i = 6; i <= 10; ++i) {            if (i < list.size()) {                list.remove(i - offset);                ++offset;            }        }        System.out.println(list);    }}

运行结果:
[0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16, 17, 18, 19]
或者:

        int offset = 0;        for (int i = 0, size = list.size(); i < size; ++i) {            if (i > 5 && i <= 10) {                list.remove(i - offset);                ++offset;            }        }

不过,还有没有其他方式呢?有没有“one-lining”一行代码就解决问题的方式呢?
有,直接使用ArrayList的removeRange方法不就可以了吗?等等,好像不可能呀,虽然JDK上有此方法,但是它有protected关键字修饰着,不能直接使用,那怎么办?看看如下代码:

package deep;import java.util.ArrayList;import java.util.List;public class Client {    public static void main(String[] args) {        List<Integer> list = new ArrayList<Integer>(20);        list.add(0);        list.add(1);        list.add(2);        list.add(3);        list.add(4);        list.add(5);        list.add(6);        list.add(7);        list.add(8);        list.add(9);        list.add(10);        list.add(11);        list.add(12);        list.add(13);        list.add(14);        list.add(15);        list.add(16);        list.add(17);        list.add(18);        list.add(19);        // 删除指定范围的元素        list.subList(6, 11).clear();        System.out.println(list);    }}

运行结果:
[0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16, 17, 18, 19]

因为subList上的所有操作都是在原始列表上进行的,那我们就用subList先取出一个子列表,然后清空。因为subList返回的List是原始列表的一个视图,删除这个视图中的所有元素,最终就会反映到原始字符串上,那么一行代码即解决问题了。

生成子列表后不要再操作原列表

在subList执行完后,如果修改了原列表的内容会怎样呢?视图是否会改变呢?如果是数据库视图,表数据变更了,视图当然会变了,至于subList生成的视图是否会改变,看如下代码:

package deep;import java.util.ArrayList;import java.util.List;public class Client {    public static void main(String[] args) {        List<String> list = new ArrayList<String>();        list.add("A");        list.add("B");        list.add("C");        List<String> subList = list.subList(0, 2);        // 原字符串增加一个元素        list.add("D");        System.out.println("原列表长度:" + list.size());        System.out.println("子列表长度:" + subList.size());    }}

运行结果:
原列表长度:4
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.ArrayListSubList.checkForComodification(ArrayList.java:1169)atjava.util.ArrayListSubList.size(ArrayList.java:998)
at deep.Client.main(Client.java:17)

什么?居然是subList的size方法出现了异常,而且还是并发修改异常?这没道理呀,这里根本就没有多线程操作,何来并发修改呢?这个问题很容易回答,那是因为subList取出的列表是原列表的一个视图,原数据集(代码中的list变量)修改了,但是subList取出的子列表不会重新生成一个新列表(这点与数据库视图是不相同的),后面在对子列表继续操作时,就会检测到修改计数器与预期的不相同,于是就抛出了并发修改异常。
出现这个问题的最终原因还是在子列表提供的size方法的检查上,还记得上面几个例子中经常提到的修改计数器吗?原因就在这里,我们来看看size的源代码:

        public int size() {            checkForComodification();            return this.size;        }

其中的checkForComodification方法就是用于检测是否并发修改的,代码如下:

        private void checkForComodification() {            //判断当前修改计算器是否与子列表生成时一致            if (ArrayList.this.modCount != this.modCount)                throw new ConcurrentModificationException();        }

this.modCount是在SubList子列表的构造函数中赋值的,其值等于生成子列表时的修改次数,如下:

    SubList(AbstractList<E> list, int fromIndex, int toIndex) {        if (fromIndex < 0)            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);        if (toIndex > list.size())            throw new IndexOutOfBoundsException("toIndex = " + toIndex);        if (fromIndex > toIndex)            throw new IllegalArgumentException("fromIndex(" + fromIndex +                                               ") > toIndex(" + toIndex + ")");        l = list;        offset = fromIndex;        size = toIndex - fromIndex;        //将原始列表的修改次数赋值给subList        this.modCount = l.modCount;    }

因此在生成子列表后再修改原始列表,ArrayList.this.modCount 必然比 this.modCount大1,不再保持相等了,于是也就抛出了ConcurrentModificationException异常。
subList的其他方法也会检测修改计数器,例如set、get、add、等方法,若生成子列表后,再修改原列表,这些方法也会抛出ConcurrentModificationException异常。
对于子列表操作,因为视图是动态生成的,生成子列表后再操作原列表,必然会导致“视图”的不稳定,最有效的方法就是通过Collections.unmodifiableList方法设置列表为只读状态,代码如下:

package deep;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;import java.util.List;public class Client {    public static void main(String[] args) {        List<String> list = new ArrayList<String>();        list.add("A");        list.add("B");        list.add("C");        List<String> subList = list.subList(0, 2);        // /设置原列表为只读状态        list = Collections.unmodifiableList(list);        // 原字符串增加一个元素        list.add("D");        System.out.println("原列表长度:" + list.size());        System.out.println("子列表长度:" + subList.size());    }}

这在团队编码中特别有用,防御式编程就是教我们如此做的。
这里还有一个问题,数据库的一张表可以有很多视图,我们的List也可以有多个视图,也就是可以有多个子列表,但问题是只要生成的子列表多于一个,则任何一个子列表就都不能修改了,否则就会抛出ConcurrentModificationException异常。

0 0