问题描述
如果我在 Java 中创建一个 2D int
数组,然后使用 Arrays.copyOf()
复制它,就像这样 -
jshell> int[][] c1 = {{1,2},{3,4}}
c1 ==> int[2][] { int[2] { 1,2 },int[2] { 3,4 } }
jshell> int[][] d1 = Arrays.copyOf(c1,c1.length)
d1 ==> int[2][] { int[2] { 1,4 } }
如果我随后更改了副本中的一个元素,为什么原始二维数组中的相应单元格会在此过程中发生变异?
jshell> d1[0][0] = 0
$21 ==> 0
jshell> d1
d1 ==> int[2][] { int[2] { 0,4 } }
jshell> c1
c1 ==> int[2][] { int[2] { 0,4 } } // c1[0][0] was 1 originally
这让我相信在使用 Arrays.copyOf()
复制二维数组时,只会为最外层数组创建一个单独的副本,而每个内部数组仍然是对原始二维数组的内部数组的引用?
jshell> d1 = null
d1 ==> null
jshell> c1
c1 ==> int[2][] { int[2] { 0,4 } }
如果是这样,为什么会这样? Arrays.copyOf()
不应该创建不同的副本,至少根据 docs 吗? Oracle 文档中是否记录了此行为?
最后,创建二维数组的不同副本的正确方法是什么,就像 Arrays.copyOf()
用于一维数组一样?
解决方法
二维数组基本上是一个包含数组的数组,而Arrays.copyOf
做的是浅拷贝,所以只拷贝了外层数组(数组的数组),而不是数组内部的值(在这种情况下, int
的数组,即 int[]
)。因此,原始和副本都包含相同的 int[]
数组,因此如果您通过其中一个进行修改,则结果也可以通过另一个看到。
javadoc 中明确提到了这一点:
对于在原始数组和数组中都有效的所有索引 复制,两个数组将包含相同的值。
您确实需要在了解签名的情况下阅读该内容:<T> T[] copyOf(T[],int)
。复制的数组是 T[]
(T
的数组,其中 T
是 int[]
),而不是 T[][]
!
对于二维数组的深拷贝,你必须自己深拷贝数组,例如见How do I do a deep copy of a 2d array in Java?
,方法Arrays.copyOf
不执行数组的深度复制,System.arraycopy
方法也不执行。您应该自己实现具有所需复制深度的数组的深度复制算法。例如:
int[][] arr1 = {{1,2},{3,4}}; // original array
int[][] arr2 = Arrays.copyOf(arr1,arr1.length); // shallow copy
int[][] arr3 = Arrays.stream(arr1) // deep copy
.map(Arrays::stream)
.map(IntStream::toArray)
.toArray(int[][]::new);
arr1[0][0] = 7;
System.out.println(Arrays.deepToString(arr1)); // [[7,2],[3,4]]
System.out.println(Arrays.deepToString(arr2)); // [[7,4]]
System.out.println(Arrays.deepToString(arr3)); // [[1,4]]
对于对象数组也是如此:
public class Test {
public static void main(String[] args) {
SomeObject[] arr1 = { // original array
new SomeObject(1),new SomeObject(2),new SomeObject(3),new SomeObject(4)};
SomeObject[] arr2 = Arrays.copyOf(arr1,arr1.length); // shallow copy
SomeObject[] arr3 = Arrays.stream(arr1) // deep copy
.mapToInt(SomeObject::getField)
.mapToObj(SomeObject::new)
.toArray(SomeObject[]::new);
arr1[0].setField(7);
System.out.println(Arrays.toString(arr1)); // [7,2,3,4]
System.out.println(Arrays.toString(arr2)); // [7,4]
System.out.println(Arrays.toString(arr3)); // [1,4]
}
static class SomeObject {
int field;
public SomeObject(int field) { this.field = field; }
public int getField() { return field; }
public void setField(int field) { this.field = field; }
public String toString() { return String.valueOf(field); }
}
}