如何在Java中复制数组

2023/06/09

1. 概述

在这个快速教程中,我们将讨论Java中不同的数组复制方法。数组复制可能看起来是一项微不足道的任务,但如果不小心,它可能会导致意外的结果和程序行为。

2. System类

让我们从核心Java库System.arrayCopy()开始,这会将一个数组从源数组复制到目标数组,从源位置开始复制操作到目标位置,直到指定的长度。

复制到目标数组的元素数等于指定的长度。它提供了一种将数组的子序列复制到另一个数组的简单方法。

如果任何数组参数为null,它会抛出NullPointerException。如果任何整数参数为负数或超出范围,它会抛出IndexOutOfBoundException。

让我们看一个使用java.util.System类将完整数组复制到另一个数组的示例:

int[] array = {23, 43, 55};
int[] copiedArray = new int[3];

System.arraycopy(array, 0, copiedArray, 0, 3);

此方法采用以下参数:源数组、要从源数组复制的起始位置、目标数组、目标数组中的起始位置以及要复制的元素数。

让我们看一下将子序列从源数组复制到目标的另一个示例:

int[] array = {23, 43, 55, 12, 65, 88, 92};
int[] copiedArray = new int[3];

System.arraycopy(array, 2, copiedArray, 0, 3);
assertTrue(3 == copiedArray.length);
assertTrue(copiedArray[0] == array[2]);
assertTrue(copiedArray[1] == array[3]);
assertTrue(copiedArray[2] == array[4]);

3. Arrays类

Arrays类也提供了多个重载方法来将一个数组复制到另一个数组。在内部,它使用我们之前检查过的System类提供的相同方法。它主要提供了两个方法,copyOf(…)和copyRangeOf(…)。

让我们先看看copyOf:

int[] array = {23, 43, 55, 12};
int newLength = array.length;

int[] copiedArray = Arrays.copyOf(array, newLength);

重要的是要注意Arrays类使用Math.min(…)选择源数组长度的最小值,并使用新长度参数的值来确定结果数组的大小。

Arrays.copyOfRange()除了源数组参数外,还有两个参数“from”和“to”。结果数组包含“from”索引,但排除“to”索引:

int[] array = {23, 43, 55, 12, 65, 88, 92};

int[] copiedArray = Arrays.copyOfRange(array, 1, 4);
assertTrue(3 == copiedArray.length);
assertTrue(copiedArray[0] == array[1]);
assertTrue(copiedArray[1] == array[2]);
assertTrue(copiedArray[2] == array[3]);

如果应用于非原始类型对象类型的数组,这两种方法都会执行对象的浅表复制:

Employee[] copiedArray = Arrays.copyOf(employees, employees.length);

employees[0].setName(employees[0].getName() + "_Changed");
 
assertArrayEquals(copiedArray, array);

由于结果是浅拷贝,因此原数组元素员工姓名的变化导致了拷贝数组的变化。

如果我们想要对非原始类型进行深度复制,我们可以选择后面各节中描述的其他选项之一。

4. 使用Object.clone()复制数组

首先,我们将使用clone方法复制一个基本类型数组:

int[] array = {23, 43, 55, 12};
 
int[] copiedArray = array.clone();

这是它有效的证明:

assertArrayEquals(copiedArray, array);
array[0] = 9;

assertTrue(copiedArray[0] != array[0]);

上面的示例表明它们在克隆后具有相同的内容,但它们持有不同的引用,因此其中一个的任何更改都不会影响另一个。

另一方面,如果我们使用相同的方法克隆一个非基本类型的数组,那么结果会有所不同。

它创建非基本类型数组元素的浅拷贝,即使封闭对象的类实现了Cloneable接口并覆盖了Object类的clone()方法。

让我们看一个例子:

public class Address implements Cloneable {
    // ...

    @Override
    protected Object clone() throws CloneNotSupportedException {
        super.clone();
        Address address = new Address();
        address.setCity(this.city);

        return address;
    }
}

我们可以通过创建一个新的addresses数组并调用我们的clone()方法来测试我们的实现:

Address[] addresses = createAddressArray();
Address[] copiedArray = addresses.clone();
addresses[0].setCity(addresses[0].getCity() + "_Changed");
assertArrayEquals(copiedArray, addresses);

此示例表明,原始数组或复制数组中的任何更改都会导致另一个数组发生更改,即使包含的对象是Cloneable也是如此。

5. 使用Stream API

事实证明,我们也可以使用Stream API来复制数组:

String[] strArray = {"orange", "red", "green'"};
String[] copiedArray = Arrays.stream(strArray).toArray(String[]::new);

对于非原始类型,它还将执行对象的浅表复制。要了解有关Java 8 Stream的更多信息,我们可以从这里开始。

6. 外部库

Apache Commons3提供了一个名为SerializationUtils的实用程序类,它提供了一个clone(…)方法。如果我们需要对非原始类型数组进行深度复制,这将非常有用。它的[Maven](https://search.maven.org/classic/#search ga 1 g%3A”org.apache.commons”ANDa%3A”commons-lang3”)依赖是:
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

让我们看一个测试用例:

public class Employee implements Serializable {
    // fields
    // standard getters and setters
}

Employee[] employees = createEmployeesArray();
Employee[] copiedArray = SerializationUtils.clone(employees);
employees[0].setName(employees[0].getName() + "_Changed");
assertFalse(copiedArray[0].getName().equals(employees[0].getName()));

此类要求每个对象都应实现Serializable接口。在性能方面,它比为对象图中的每个对象手动编写的克隆方法要慢。

7. 总结

在本文中,我们讨论了在Java中复制数组的各种选项。

我们选择使用的方法主要取决于具体的场景。只要我们使用原始类型数组,我们就可以使用System和Arrays类提供的任何方法。性能上应该没有任何差异。

对于非基本类型,如果我们需要对数组进行深拷贝,我们可以使用SerializationUtils或显式地向我们的类添加克隆方法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章