【问题标题】:Why changes in sublist are reflected in the original list?为什么子列表的变化会反映在原始列表中?
【发布时间】:2015-10-11 00:08:51
【问题描述】:

我知道当您通过引用传递 Collections 时,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);

【问题讨论】:

    标签: java list arraylist sublist


    【解决方案1】:

    根据JavaDoc关于此事:

    List 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 更改您的元素,这同样适用。

    【讨论】:

    • 你是什么意思在内存中发生了什么?就像引用相同事物的任何事物一样。这里没有什么特别的
    • @ShubhamKharde:我试图扩展我的答案。如果您仍然有问题,您将需要研究一般的参考是如何工作的。
    • @Brandon Ling 这些变化将如何体现?引用、子字符串和主列表都必须指向同一个对象?或者子列表对象包含类似 pointers 的东西来存储存储相应对象值的引用?
    • 小问题,为什么sublist(0, 2)中的列表有4个元素?
    • @phlaxyr:这是一个早该出现的错字。固定。
    【解决方案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&lt;E&gt; 是在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());
    }
    

    这将在内部调用 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 持有对调用subList 的ArrayList 的引用。如此有效地调用clear,您正在从创建子列表的原始列表中调用removeRange

    【讨论】:

    • 谢谢。人们只是不想深入,如果他们的答案不被接受,他们就会对问题投反对票。对于那些对问题投反对票的人,这是我所期待的答案,他们必须坚持所提出的问题。
    • @ShubhamKharde 人们通常不会仅仅因为他们的答案未被接受而投反对票,而是如果他们认为该问题不清楚,或者不包含一些证据或研究。老实说,我也不确定这是否是您正在寻找的答案,但由于我认为刷新我对收藏的记忆会很好,所以我决定做一些研究并将它们作为答案发布。
    • @ShubhamKharde 看起来很相似,他正在使用内存地址描述代码,我正在展示哪些方法,何时以及在哪些引用被调用以向您展示要点,最终调用 list2.clear()将导致从原始list 调用removeRange
    • 要点是sibList 不会创建从原始列表复制对所有对象的引用的列表,而只是记住它应该表示的列表(范围)中第一个和最后一个元素的索引。所以在 subList 上调用方法与在一定范围的原始列表上调用这些方法是一样的。
    【解决方案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);
    


    list2 = list.subList(2, 5);
    

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


    list2.clear();
    

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

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

    【讨论】:

      【解决方案4】:

      检查这个link

      SubList 返回此列表部分的视图 指定 fromIndex(包括)和 toIndex(不包括)。 (如果从索引 和 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]
      

      【讨论】:

        猜你喜欢
        • 2013-05-13
        • 1970-01-01
        • 1970-01-01
        • 2012-08-18
        • 1970-01-01
        • 2020-12-13
        • 1970-01-01
        • 2020-01-29
        • 2021-01-15
        相关资源
        最近更新 更多