Java中的ArrayList嵌套ArrayList

39
以下代码输出
[[100, 200, 300], [100, 200, 300]]. 

然而,我期望的是

[[100, 200, 300], [100, 200]], 
我哪里做错了?
public static void main(String[] args) {
    ArrayList<ArrayList<Integer>> outer = new ArrayList<ArrayList<Integer>>();
    ArrayList<Integer> inner = new ArrayList<Integer>();        

    inner.add(100);     
    inner.add(200);

    outer.add(inner);
    outer.add(inner);

    outer.get(0).add(300);

    System.out.println(outer);

}

5
请尝试使用outer.add(new ArrayList<String>(inner));。这行代码的意思是将内部List的内容复制到一个新的ArrayList中,然后将其添加到外部List中。 - Keppil
4个回答

55
你正在向外部列表中添加对同一个内部 ArrayList 的引用两次。因此,当你改变内部列表(通过添加300),你会在“两个”内部列表中看到它(实际上只有一个内部列表,其引用被存储在外部列表中)。
为了获得所需的结果,您应该创建一个新的内部列表:
public static void main(String[] args) {
    ArrayList<ArrayList<Integer>> outer = new ArrayList<ArrayList<Integer>>();
    ArrayList<Integer> inner = new ArrayList<Integer>();        

    inner.add(100);     
    inner.add(200);
    outer.add(inner); // add first list
    inner = new ArrayList<Integer>(inner); // create a new inner list that has the same content as  
                                           // the original inner list
    outer.add(inner); // add second list

    outer.get(0).add(300); // changes only the first inner list

    System.out.println(outer);
}

52

在阅读本答案之前,您可能需要了解变量、对象和引用之间的区别是什么?


这就是您现在拥有的内容。

ArrayList<ArrayList<Integer>> outer = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> inner = new ArrayList<Integer>();        

会创建

outer -> []
inner -> []

该代码创建了两个独立的列表,并将它们的引用存储在outerinner变量中。


之后

inner.add(100);     
inner.add(200);

你的情况看起来像是

outer -> []
inner -> [100, 200]

这里开始是令人困惑的部分

outer.add(inner);
outer.add(inner);

这里内部值inner变量(也就是指向列表 [100, 200] 的引用)被放置在outer列表中两次。这意味着outer指向[100, 200]列表的指针有两个。

//note: `reference1` == `reference2` (like 42 == 42) since they point to same object
outer -> [ reference1, reference2 ] 
              |             |
      +-------+             |
      +---------------------+
      ↓
inner +-> [100, 200]

这意味着如果您更改列表 [100,200] 的状态,则可以使用 outer.get(0)outer.get(1)inner 查看这些更改,因为它们都是指向同一列表的引用。

因此,如果我们使用

outer.get(0).add(300);

outer.get(0) 返回指向与 inner 相同的列表的引用,add(300) 在该列表中添加了一个新元素。这意味着在执行此操作后,新情况将如下所示:

outer -> [ reference1 , reference2 ]
              |             |
      +-------+             |
      +---------------------+
      ↓
inner -> [100, 200, 300]

这就是为什么打印outer时你看到的

[[100, 200, 300], [100, 200, 300]]. 
 ^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^
   from get(0)      from get(1)

你实际上需要创建两个单独的列表,这样reference1reference2将会指向两个不同的列表。例如:

outer  -> []
inner1 -> [100, 200]
inner2 -> [100, 200]

这将稍后组织成

outer -> [ reference1 , reference2 ]
              |             |
       +------+             |
       ↓                    |
inner1 -> [100, 200]        |
                            |
       +--------------------+
       ↓
inner2 -> [100, 200]

你可以这样做

List<List<Integer>> outer = new ArrayList<List<Integer>>();
List<Integer> inner1 = new ArrayList<Integer>();
List<Integer> inner2 = new ArrayList<Integer>();

inner1.add(100);
inner1.add(200);

inner2.add(100);
inner2.add(200);

outer.add(inner1);
outer.add(inner2);

outer.get(0).add(300);

System.out.println(outer);
//Output: [[100, 200, 300], [100, 200]]

9
视觉是一切。 - filip
非常棒的可视化效果,点赞!但是有一个小错别字:应该是 from get(**1**) 而不是 from get(**2**) - winklerrr
@winklerrr 谢谢。已更新 :) - Pshemo

10

命令outer.add(inner)添加的是对inner引用,而不是它的副本。

因此,当您将两个对inner的引用添加到ArrayList outer中时,您正在添加两个相同的东西。通过outer.get(0)修改inner也会修改outer.get(1)中的值,因为它们指向同一个东西。

如果您创建inner的副本并使用它,那么您将拥有两个不同的实例,并能够分别修改它们。您可以使用一个简单的命令来完成这个操作:

outer.add(new ArrayList<[var type]>(inner));

new ArrayList(inner) 的指令会创建一个新的ArrayList,并将inner的内容放入其中 - 但不使用与inner相同的实例。因此,您将保留内容,但不保留重复的引用。

通过添加副本而不是引用,您可以修改副本而不修改您可能称之为“原始”的内容。


0

在IDE中尝试这个示例:

ArrayList<ArrayList<String>> ext = new ArrayList<ArrayList<String>>();
    String[] arr = {"1","2","3","4","5","6"};
    int n=arr.length;
    for(int i=0;i<n;i++){
        ArrayList temp = new ArrayList<String>();
        for(int j=i;j<n;j++){
            temp.add(arr[j]);
            // this the line that needs to look at. Rather than pointing to the same memory reference creating a new ArrayList will store it in a different memory location. If you just add temp then it will point to same memory location.
            ext.add(new ArrayList<String>(temp));
             // Comment above line and uncomment below to see the difference    
            //ext.add(temp);
        }

    }
    System.out.println(ext);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接