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

时间:2015-07-21 13:39:32

标签: java list arraylist sublist

我知道当你通过引用传递它们时,Java中的Collections是可变的 我想确切地知道原始列表和子列表的内存地址中发生了什么 子列表和原始列表是否引用同一个对象?

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

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

4 个答案:

答案 0 :(得分:8)

根据问题的JavaDoc

  

列出subList(int fromIndex,                   int toIndex)

     

返回指定fromIndex(包含)和toIndex之间此列表部分的视图,   独家。 (如果fromIndex和toIndex相等,则返回的列表为   空。)返回的列表由此列表支持,因此非结构化   返回列表中的更改将反映在此列表中,并且   反之亦然即可。返回的列表支持所有可选列表   此列表支持的操作。

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

编辑:根据您的评论,假设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更改元素,也同样如此。

答案 1 :(得分:3)

这是基于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);

enter image description here


list2 = list.subList(2, 5);

SubList引用原始列表,并带有偏移量以了解子列表的起始位置,并提供一个大小来了解子列表的终止位置。

enter image description here


list2.clear();

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

enter image description here

请注意,通过从索引5到索引2处理引用并用空值填充数组的索引3、4和5来删除此元素。

答案 2 :(得分:2)

选中link

  

SubList返回此列表之间的部分视图   指定fromIndex,包含和toIndex,独占。 (如果是fromIndex   和toIndex相等,返回的列表为空。)返回的列表   由此列表支持,因此返回列表中的非结构更改   反映在此列表中,反之亦然。返回的列表支持   此列表支持的所有可选列表操作。

所以你的list2只是你的原始列表的子视图。这就是为什么当你清除list2时,你在原始列表中丢失了相应的值。检查这段代码。

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]

答案 3 :(得分:1)

排队

list2 = list.subList(2, 5);

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

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>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字段将存储对原始ArrayListnew SubList(this, ...))的引用。

现在打电话

list2.clear();
来自clear()的{​​{1}}继承的SubList方法的代码将被调用

AbstractList

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

内部调用removeRange覆盖
SubList

如您所见,结果您正在致电

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的ArrayList的引用。通过调用subList,您可以从您创建子列表的原始列表中调用clear