相信很多人在初学java的时候,都会遇到一个同样的问题,就是当我们认为自己已经把一个list加入到List<List<T>> 中去了,为什么最后结果发现不对。如这个例子。
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 3; i++) {
list.add(i);
res.add(list);
}
return res;
假设,我们想得到的数据是{ {0}, {0, 1}, {0, 1, 2} } 这样的结果,请问这个res是不是得到了这样的结果?
如果不是,
请问,res.size()==?,然后res里的数据是什么样的?
首先,这里只通过ArrayList构造器创建了一个List的实例,也就是说整个过程中,只有一个真正意义上的List Object,但是res.size() == 3。
不仅如此,res == { {0, 1, 2}, {0, 1, 2}, {0, 1, 2} };
本人在刚开始学习的过程中,难以预计到这样的结果。
其实原因也很简单。在java中,有不同的数据类型。其中,原始数据类型之间是值传递,而其他的数据类型是引用的“值”传递。(Passing reference doesn't apply to java)
而在java中,一个Object或者Array被创建时,将会在堆中分配内存,同时产生一个该Object或者Array的引用变量(reference)的值放在函数的栈内存中。
也就是说,list只是一个相对应类别的引用名称,或者说是一个句柄(handle)。
当res.add(list)发生时,一个引用变量被传入。在for循环中,被传入了三次。即使在传入的过程中,list所指向的Object所包含的内容不同,却是同一个具体的实例,传入的是Object的值。
最终res里包含的就是三次在return位置时,list所指向的Object的值,也就是 {0, 1, 2}。
为了避免这样的结果导致实际工作中的错误,一般而言,
res.add(new ArrayList<Integer>(list));
在加入一个"引用"型变量时,产生一个新的Object来避免问题的出现。
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 3; i++) {
list.add(i);
res.add(new ArrayList<Integer>(list));
}
return res;
// res == { { 0 }, { 0, 1 }, { 0, 1, 2} }