首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >subList()与其他相似集合方法有不同的行为,一旦subList被更改

subList()与其他相似集合方法有不同的行为,一旦subList被更改
EN

Stack Overflow用户
提问于 2013-08-21 20:35:42
回答 4查看 946关注 0票数 3

有人能解释为什么subList()不是subSet()方法,而是抛出一个subSet,而subSet不是。这两个方法都创建了一个支持集合,因此可能subList()方法设计人员依赖不可修改的原始列表创建了这个方法,但是如果所有的支持集合都具有相同的行为(比如subSet()),那么不会更好吗?

//代码

代码语言:javascript
复制
public class ConcurrentModificationException {
public static void main(String[] args) {
    String[] array = {"Java","Python","Pearl","Ada","Javascript","Go","Clojure"};
    subListEx(array);
    subSetEx(array);
}

private static void subListEx(String[] array) {
    List<String> l = new ArrayList<String>(Arrays.asList(array));
    List<String> l2 = l.subList(2, 4);
    System.out.println(l.getClass().getName());

    // l.add("Ruby"); // ConcurrentModificationException
    // l.remove(2); // ConcurrentModificationException
    l2.remove("Ada"); // OK
    for (String s:l) { System.out.print(s+", "); } 
    System.out.println();
    for (String s:l2) { System.out.print(s+", "); }
}

private static void subSetEx(String[] array) {
    SortedSet<String> s1 = new TreeSet<String>(Arrays.asList(array));
    SortedSet<String> s2 = s1.subSet("Java", "Python");
    s1.remove("Ada");

    for (String s:s1) { System.out.print(s+", "); } 
    System.out.println();
    for (String s:s2) { System.out.print(s+", "); }
}}

提前感谢!

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-08-21 21:07:57

现在已经很清楚的是,这种行为是按照文献记录的。但我认为你的主要问题是为什么ArrayListTreeSet的行为不同。好吧,这与数据如何在两个集合内部存储有关。

ArrayList内部使用一个数组来存储数据,随着ArrayList大小的动态增加,数据的大小会进行调整。现在,当您创建给定列表的subList时,具有指定索引的原始列表与subList关联。因此,在原始列表中执行的任何结构更改(对原始数组的索引)都将使作为子列表的一部分存储的索引变得毫无意义。这就是为什么在ArrayList#subList方法中不允许进行任何结构更改的原因。subList方法在inner类中返回一个名为SubListinner类的实例,如下所示:

代码语言:javascript
复制
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;
    }

如您所见,SubList包含对原始列表的引用。而parentOffset只不过是您正在创建的subList的起始索引。现在,修改原始list可能会更改原始列表中的fromIndex值,而不是SubList中的值。在这种情况下,parentOffset类中的SubList和原始列表中的fromIndex将指向不同的数组元素。还可能在某个时候,原始数组变得足够短,使存储在SubList中的索引无效,并使其成为OutOfRange。这当然是不可取的,并且返回的subList的语义被认为是未定义的,对于原始列表的这种结构更改。

另一方面,TreeSet在内部将其数据存储在TreeMap中。现在,由于在Map中没有这样的指数概念,所以不存在指数分解的问题。Map不过是键值对的映射.创建SubSet涉及到创建一个由原始Map支持的SubMap。修改原始Set只需要相应的键值映射无效,从而将更改传播到为subSet创建的subMap

票数 4
EN

Stack Overflow用户

发布于 2013-08-21 20:52:26

List.subList(int, int)的合同涵盖了这一点。这是相关的部分,重点是我。

返回的列表由此列表支持,因此返回列表中的非结构更改反映在此列表中,反之亦然。 ..。 如果支持列表(即该列表)是结构修改的,则此方法返回的列表的语义变得不明确,而不是通过返回的列表。(结构修改是那些改变列表大小的修改,或者以其他方式干扰它,以致正在进行的迭代可能产生不正确的结果。)

在您的示例中,您正在对支持列表进行结构更改,因此结果未定义。

票数 1
EN

Stack Overflow用户

发布于 2013-08-21 20:53:54

文档非常清楚:aList.subList()返回列表的视图。返回的对象是由原始列表支持的,或者您应该假设。

关于Set的文档也非常清楚:

这个类的迭代器方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时候对集合进行修改,那么除了通过迭代器自己的remove方法之外,以任何方式,迭代器将抛出一个ConcurrentModificationException。

为什么用List来制造问题比用Set容易呢?因为在Set中,删除元素的效果非常明显:元素不再存在于集合中,无论是对于set还是任何子集。插入操作也是如此。但是,当您从List中删除一个元素时,它是否意味着子列表相应地调整它们的索引(可能还包括大小),或者只是通过从右边或左边添加元素来保持它们的大小?在第二种情况下,如果支持名单变得太短怎么办?行为太复杂,无法定义。而且对于实现来说也太有限了。

例如:一个列表是a,b,c,d,子列表是list.subList(1,3) (b,c)。a已从列表中删除。清单中的效果是明确的。但是子列表是保持b,c,还是成为c,d

例如:一个列表是a,b,c,d,子列表是list.subList(1,3) (b,c)。b已从列表中删除。同样,列表中的效果也是显而易见的。但是子列表是否变成Ca,cc,d

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18367173

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档