为什么子列表中的更改会反映在原始列表中?

Why changes in sublist are reflected in the original list?

我知道 Java 中的 Collections 在通过引用传递它们时是可变的。
我想知道原始列表的内存地址和它的sublist/s到底发生了什么。
子列表和原始列表引用同一个对象吗?

以下代码示例反映了子列表对主原始列表所做的更改。

List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add(1, "3");

List<String> list2 = new LinkedList<String>(list);

list.addAll(list2);

list2 = list.subList(2, 5);
list2.clear();               //Changes are made to list

System.out.println(list);

根据 JavaDoc 关于此事:

List subList(int fromIndex, int toIndex)

Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive. (If fromIndex and toIndex 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.

子列表将指向原始列表中存在的相同元素,因此,通过子列表所做的任何更改都将反映在原始列表中,因为您正在更改相同的对象。

编辑:根据您的评论,假设 original list 具有以下引用:0x00 0x01 0x02 0x03 0x04 0x05 并且这些映射到对象存在的内存位置。

在上面执行 sublist(0, 2) 将产生一个列表,其中包含指向以下内存位置的指针 0x00 0x01 0x02,这与 original list 中的相同。

这意味着,如果您执行 sublist.get(0).setFoo(foo),这将反过来寻找 0x00 处的对象并设置一些 属性。然而,0x00 也被 original list 引用,这就是为什么更改子列表意味着您将更改源列表 ,因为两个列表指向相同的对象。如果您通过 original list.

更改元素,这同样适用

检查此 link

SubList returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive. (If fromIndex and toIndex 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.

所以您的 list2 只是原始 list.That 的子视图,这就是为什么当您清除 list2 时,您在原始 list.Check 代码中丢失了相应的值。

public static void main(String[] args)
    {   
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add(1, "3");
        List<String> list2 = new LinkedList<String>(list);
        list.addAll(list2);
        System.out.println(list);
        list2 = list.subList(2, 5);
        System.out.println(list2);
        list2.clear();               //Changes are made to list1
        System.out.println(list);

    }

O/P

[1, 3, 2, 1, 3, 2]
[2, 1, 3]
[1, 3, 2]

在线

list2 = list.subList(2, 5);

您正在调用从 list 引用的 ArrayListsubList 方法。它的代码看起来像这样

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

所以在确认有效范围后 list2 将存储

的结果
new SubList(this, 0, fromIndex, toIndex);

其中 private class SubList extends AbstractList<E> 是 class 在 ArrayList 中定义的,此构造函数的代码如下所示

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;
}

所以它的 parent 字段将存储对原始 ArrayList (new SubList(<b>this</b>, ...)).

现在当你打电话时

list2.clear();

SubListAbstractList继承的clear()方法的代码将被调用

public void clear() {
    removeRange(0, size());
}

这将在内部调用 removeRangeSubList

中覆盖
protected void removeRange(int fromIndex, int toIndex) {
    checkForComodification();
    parent.removeRange(parentOffset + fromIndex,
                       parentOffset + toIndex);
    this.modCount = parent.modCount;
    this.size -= toIndex - fromIndex;
}

如您所见,您调用的结果是

parent.removeRange(parentOffset + fromIndex,
                   parentOffset + toIndex);

如您所记得,parent 包含对调用 subList 的 ArrayList 的引用。如此有效地调用 clear 是从创建子列表的原始列表调用 removeRange

下面是基于 java source code 的代码示例内存的简化可视化,作为对 Pshemo 出色答案的补充:

List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add(1, "3");

List<String> list2 = new LinkedList<String>(list);

list.addAll(list2);


list2 = list.subList(2, 5);

SubList 具有对原始列表的引用,其中包含一个偏移量以了解子列表的开始位置和一个大小以了解子列表的结束位置。


list2.clear();

对列表元素的操作被转发到原始列表。

请注意,通过将索引 5 的引用复制到索引 2 并用空值填充数组的索引 3、4 和 5 来删除这些元素。