为什么ArrayList副本的行为与整数副本的行为不同?

时间:2017-07-02 02:25:50

标签: java arraylist reference

我遇到了一个问题,简化了这样的事情:

public static void main(String[] args) {
    ArrayList<String> firstList = new ArrayList<>();
    firstList.add("Hello");
    firstList.add("World");
    firstList.add("This");
    firstList.add("Is");
    firstList.add("A");
    firstList.add("Test");

    System.out.println("Size of firstList: " + firstList.size());

    ArrayList<String> tempList = firstList;
    System.out.println("Size of tempList: " + tempList.size());

    firstList.clear();
    System.out.println("Size of firstList: " + firstList.size()); // should be 0
    System.out.println("Size of tempList: " + tempList.size());
}

输出是:

Size of firstList: 6
Size of tempList: 6
Size of firstList: 0
Size of tempList: 0

我希望第二轮tempList的大小为6而不是0

已经有一些与此效果相关的问题,例如this oneanother one

从答案中我发现这是因为tempList引用与firstList相同的引用,因此当firstList更改tempList时(正确)我,如果我错在这里。)

因此,更好的解决办法就是:

ArrayList<String> tempList = new ArrayList<String>(firstList);

如果上面提到的参考信息是真的,那么为什么这个代码:

public static void main(String[] args) {
    int firstValue = 5;

    System.out.println("firstValue: " + firstValue);

    int tempValue = firstValue;
    System.out.println("tempValue: " + firstValue);

    firstValue = 3;
    System.out.println("firstValue: " + firstValue);
    System.out.println("tempValue: " + tempValue);
}

给出这个输出:

firstValue: 5
tempValue: 5
firstValue: 3
tempValue: 5

第二次打印时tempValue也不应该是3吗?

我觉得我误解了引用是如何工作的,所以有人可以解释为什么第一个例子中的临时列表和原始列表一起受到影响,而临时整数和原始整数给出不同的结果,如第二个例子中所示?

4 个答案:

答案 0 :(得分:2)

Java有两种变量类型:基元和引用。将一个变量分配给另一个变量时,如

ArrayList<String> tempList = firstList;

您将firstList中存储的引用分配为变量tempList的值。请注意,您没有使用此分配创建新的ArrayList对象,这一点非常重要;你只是复制一个参考值。

当您使用基元进行分配时:

int tempValue = firstValue;

你正在做同样的事情,但是具有原始价值。

一般来说,这样想。变量不共享内存位置;每个变量都有自己的值。但是,当值是引用时,两个引用变量可能包含相同的引用(就像两个原始变量可能包含相同的原始值,例如5)。对于引用类型,如果使用一个变量来更改所引用对象的内容,则在通过第二个变量访问时,您将看到该对象的更改。在计算机科学文献中,这种行为称为aliasing。对于基元,这种混叠不会发生。

答案 1 :(得分:1)

对于ArrayList示例,两个变量指向到堆内存中其他位置的同一对象,因此更改一个引用的内容会影响另一个。

对于整数示例,当前行为是预期的,因为它们是基本类型而不是引用类型。当涉及原始类型时,变量存储值本身而不是对内存中对象的引用。

答案 2 :(得分:1)

int tempValue是一种原始类型,这意味着它直接按值存储。 ArrayList tempList不是原始类型,因此通过引用存储。

您在int看到的情况也会发生在所有其他Java静态类型的原始变量中。同时,只要它们引用的实例的值发生变化,Java非原始变量就会随着变异而发生变化。

(这会产生一个后续问题:当您使用tempList代替firstList = new ArrayList()时,firstList.clear()会发生什么?重新分配和变异之间的值是否相同?为什么?)

答案 3 :(得分:0)

因为原始值没有引用,并且包装器类型是不可变的(因此Integer的行为方式相同)。作为反例,数组是引用类型(甚至是基元数组)。所以,

int[] arr = { 1 };
int[] arrCopy = Arrays.copyOf(arr, arr.length); // <-- deep copy
int[] b = arr; // <-- shallow copy
b[0] = 2; // <-- same as arr[0] = 2
System.out.printf("arr = %s, arrCopy = %s, b = %s%n", //
        Arrays.toString(arr), Arrays.toString(arrCopy), //
        Arrays.toString(b));

输出

arr = [2], arrCopy = [1], b = [2]

另见What is the difference between a deep copy and a shallow copy?