日期:2014-05-20  浏览次数:20988 次

大家来评论下ArrayList中的subList的设计
如下代码
Java code

    List l = new ArrayList();
    l.add("a");
    l.add("b");
    l.add("c");
    List s = l.subList(0, 2);
    l.remove(0);
    l.add("d");
    System.out.println(s.size());


将会抛出java.util.ConcurrentModificationException
看了下源码,发现subList只是复制了引用,并且新生产的subList也是一个ArrayList实例
这样子的话,如果一个ArrayList调用了subList之后,岂不是代表不能再对两个中的任一个list进行修改(即调用add或remove方法)了,原因如下:
假设一个ArrayList A 调用了subList之后生成一个B,由于A,B都是ArrayList实例,并且A和B都是“快速失败”的,而且A和B共用了一块部分,如果此时修改A,B将会抛ConcurrentModificationException,同理修改B,A也会抛异常。
不知道我的理解对不对,如果没理解错的话,这种设计岂不是很糟糕。以后岂不是调用subList要很小心了。

------解决方案--------------------
这是API的解释:
* Returns a view of the portion of this list between the specified
* <tt>fromIndex</tt>, inclusive, and <tt>toIndex</tt>, exclusive. (If
* <tt>fromIndex</tt> and <tt>toIndex</tt> are equal, the returned list is
* empty.) The returned list is backed by this list, so non-structural
* changes in the returned list are reflected in this list, and vice-versa.
* The returned list supports all of the optional list operations supported
* by this list.<p>

测试:
Java code

 List l = new ArrayList();
           l.add("a");
           l.add("b");
            l.add("c");
            List s = l.subList(0, 2);
            s.add("d");
            System.out.println(l.size());
            System.out.println(s.size());

------解决方案--------------------
API的中文说明:

返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。(如果 fromIndex 和 toIndex 相等,则返回的列表为空)。返回的列表由此列表支持,因此返回列表中的非结构性更改将反映在此列表中,反之亦然。返回的列表支持此列表支持的所有可选列表操作。
此方法省去了显式范围操作(此操作通常针对数组存在)。通过传递 subList 视图而非整个列表,期望列表的任何操作可用作范围操作。例如,下面的语句从列表中移除了元素的范围: 

list.subList(from, to).clear();
 可以对 indexOf 和 lastIndexOf 构造类似的语句,而且 Collections 类中的所有算法都可以应用于 subList。
如果支持列表(即此列表)通过任何其他方式(而不是通过返回的列表)从结构上修改,则此方法返回的列表语义将变为未定义(从结构上修改是指更改列表的大小,或者以其他方式打乱列表,使正在进行的迭代产生错误的结果)。 

所以可以像2楼那样用s进行添加或者删除操作。
------解决方案--------------------
探讨

引用:

The returned list is backed by this list, so non-structural
changes in the returned list are reflected in this list, and vice-versa.

如果把你的代码s.add("d");改成l.add("d");就会抛Concur……

------解决方案--------------------
既然你是为了高效便捷而去引用subList,那必然在灵活性上要有所牺牲。

如果你强调灵活性,那不如直接复制出来,就可以随便玩了,但必然效率和空间要做微量牺牲。

说到底都是权衡选择的问题。