java - subList() 与其他类似的 Collections 方法有不同的行为,一旦 subList 被改变

标签 java collections

谁能解释为什么 subList() 不像 subSet() 方法那样运行,并抛出 ConcurrentModificationException,而 子集 不是。这两种方法都创建了一个 Backed Collection,所以可能 subList() 方法 设计者创建这个方法依赖于不可修改的原始列表,但如果所有 Backed Collections 具有相同的行为(如 subSet())?

//代码

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+", "); }
}}

提前致谢!

最佳答案

很明显,行为符合记录。但我认为您的主要问题是为什么 ArrayListTreeSet 的行为不同。好吧,这与数据在两个集合中的内部存储方式有关。

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

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 中的值。在这种情况下,SubList 类中的 parentOffset 和原始列表中的 fromIndex 将指向不同的数组元素。也有可能在某个时候原始数组变得足够短,使 SubList 中存储的索引无效并使其 OutOfRange。这当然是不可取的,并且在对原始列表进行此类结构更改时,返回的子列表的语义被认为是未定义

另一方面,TreeSet 在内部将其数据存储在 TreeMap 中。现在,由于 Map 中没有这样的索引概念,因此不存在索引中断的问题。 Map 只不过是 key-value 对的映射。创建一个 SubSet 涉及创建一个由原始 Map 支持的 SubMap。修改原始 Set 只需要使相应的 key-value 映射无效,从而将更改传播到为 创建的 subMap子集.

关于java - subList() 与其他类似的 Collections 方法有不同的行为,一旦 subList 被改变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18367173/

相关文章:

java - LinkedHashSets的HashSet不删除具有多个元素的LinkedHashSet

ios - Swift 中泛型关联类型的比较类型

关于清除链表的 Java 最佳实践

java - 使用其他包名称导入 eclipse 项目? - eclipse /安卓

c# - 这段从 Java 到 C# 的代码片段正确吗?

java - URL参数字符串的正则表达式java

java - 从菜单中选择帮助(警报对话框)选项时,应用程序停止工作

c# - 如何将 List<Company> 转换为 List<ICompany>

Java map : multithreading juggler

java - 为什么我得到 java.net.SocketException : Connection reset when deploying app?