Contents

Java 中复制数组

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()*除了源数组参数外,还接受 2 个参数,“ 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() 进行数组复制

Object.clone()继承自数组中的Object类。

让我们首先使用 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;
    }
}

我们可以通过创建一个新的地址数组并调用我们的*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 Streams的更多信息,您可以从这里 开始。

6. 外部库

Apache Commons 3提供了一个名为SerializationUtils的实用程序类,它提供了一个*clone(…)*方法。如果我们需要对非原始类型数组进行深拷贝,这将非常有用。它可以从这里 下载,它的 Maven 依赖项是:

<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接口。在性能方面,它比为我们的对象图中的每个对象手动编写的复制方法要慢。