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

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

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.ArrayList SubList.checkForComodification(ArrayList.java:1169)atjava.util.ArrayList SubList.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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值