1. Introduction
1.介绍
When we want to copy an object in Java, there are two possibilities that we need to consider, a shallow copy and a deep copy.
当我们想在Java中复制一个对象时,有两种可能性需要考虑,浅拷贝和深拷贝。
For the shallow copy approach, we only copy field values, therefore the copy might be dependant on the original object. In the deep copy approach, we make sure that all the objects in the tree are deeply copied, so the copy isn’t dependant on any earlier existing object that might ever change.
对于浅层拷贝的方法,我们只拷贝字段值,因此拷贝可能依赖于原始对象。在深层复制方法中,我们确保树中的所有对象都被深层复制,因此副本不依赖于任何可能改变的早期现有对象。
In this tutorial, we’ll compare these two approaches, and learn four methods to implement the deep copy.
在本教程中,我们将比较这两种方法,并学习四种方法来实现深度复制。
2. Maven Setup
2.Maven的设置
We’ll use three Maven dependencies, Gson, Jackson, and Apache Commons Lang, to test different ways of performing a deep copy.
我们将使用Gson、Jackson和Apache Commons Lang这三个Maven依赖项来测试执行深度拷贝的不同方式。
Let’s add these dependencies to our pom.xml:
让我们把这些依赖性添加到我们的pom.xml。
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
The latest versions of Gson, Jackson, and Apache Commons Lang can be found on Maven Central.
Gson、Jackson和Apache Commons Lang的最新版本可以在Maven中心找到。
3. Model
3.模型
To compare different methods of copying Java objects, we’ll need two classes to work on:
为了比较复制Java对象的不同方法,我们将需要两个类来进行工作。
class Address {
private String street;
private String city;
private String country;
// standard constructors, getters and setters
}
class User {
private String firstName;
private String lastName;
private Address address;
// standard constructors, getters and setters
}
4. Shallow Copy
4.浅层复制
A shallow copy is one in which we only copy values of fields from one object to another:
浅层拷贝是指我们只将字段的值从一个对象拷贝到另一个对象。
@Test
public void whenShallowCopying_thenObjectsShouldNotBeSame() {
Address address = new Address("Downing St 10", "London", "England");
User pm = new User("Prime", "Minister", address);
User shallowCopy = new User(
pm.getFirstName(), pm.getLastName(), pm.getAddress());
assertThat(shallowCopy)
.isNotSameAs(pm);
}
In this case, pm != shallowCopy, which means that they’re different objects; however, the problem is that when we change any of the original address’ properties, this will also affect the shallowCopy‘s address.
在这种情况下,pm != shallowCopy,这意味着它们是不同的对象;然而,问题是,当我们改变任何原始的address属性时,这也将影响到shallowCopy的地址。
We wouldn’t bother with it if Address was immutable, but it’s not:
如果Address是不可变的,我们就不会去管它,但它不是。
@Test
public void whenModifyingOriginalObject_ThenCopyShouldChange() {
Address address = new Address("Downing St 10", "London", "England");
User pm = new User("Prime", "Minister", address);
User shallowCopy = new User(
pm.getFirstName(), pm.getLastName(), pm.getAddress());
address.setCountry("Great Britain");
assertThat(shallowCopy.getAddress().getCountry())
.isEqualTo(pm.getAddress().getCountry());
}
5. Deep Copy
5.深度复制
A deep copy is an alternative that solves this problem. Its advantage is that each mutable object in the object graph is recursively copied.
深度复制是解决这个问题的一个替代方案。它的优点是,对象图中的每个可变对象都是递归复制的。
Since the copy isn’t dependent on any mutable object that was created earlier, it won’t get modified by accident like we saw with the shallow copy.
由于该副本不依赖于先前创建的任何可变对象,它不会像我们在浅层副本中看到的那样被意外地修改。
In the following sections, we’ll discuss several deep copy implementations and demonstrate this advantage.
在下面的章节中,我们将讨论几个深度拷贝的实现,并展示这一优势。
5.1. Copy Constructor
5.1.复制构造函数
The first implementation we’ll examine is based on copy constructors:
我们要研究的第一个实现是基于复制构造函数的。
public Address(Address that) {
this(that.getStreet(), that.getCity(), that.getCountry());
}
public User(User that) {
this(that.getFirstName(), that.getLastName(), new Address(that.getAddress()));
}
In the above implementation of the deep copy, we haven’t created new Strings in our copy constructor because String is an immutable class.
在上述深度复制的实现中,我们没有在复制构造函数中创建新的String,因为String是一个不可变的类。
As a result, they can’t be modified by accident. Let’s see if this works:
因此,它们不可能被意外地修改。让我们看看这是否有效。
@Test
public void whenModifyingOriginalObject_thenCopyShouldNotChange() {
Address address = new Address("Downing St 10", "London", "England");
User pm = new User("Prime", "Minister", address);
User deepCopy = new User(pm);
address.setCountry("Great Britain");
assertNotEquals(
pm.getAddress().getCountry(),
deepCopy.getAddress().getCountry());
}
5.2. Cloneable Interface
5.2.可克隆的界面
The second implementation is based on the clone method inherited from Object. It’s protected, but we need to override it as public.
第二个实现是基于从Object继承的clone方法。它是受保护的,但我们需要将其覆盖为public。
We’ll also add a marker interface, Cloneable, to the classes to indicate that the classes are actually cloneable.
我们还将为类添加一个标记接口,Cloneable,,以表明这些类实际上是可以克隆的。
Let’s add the clone() method to the Address class:
让我们把clone()方法添加到Address类中。
@Override
public Object clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
return new Address(this.street, this.getCity(), this.getCountry());
}
}
Now let’s implement clone() for the User class:
现在让我们为User类实现clone()。
@Override
public Object clone() {
User user = null;
try {
user = (User) super.clone();
} catch (CloneNotSupportedException e) {
user = new User(
this.getFirstName(), this.getLastName(), this.getAddress());
}
user.address = (Address) this.address.clone();
return user;
}
Note that the super.clone() call returns a shallow copy of an object, but we set deep copies of mutable fields manually, so the result is correct:
注意,super.clone()调用返回一个对象的浅层副本,但是我们手动设置了可变域的深层副本,所以结果是正确的:。
@Test
public void whenModifyingOriginalObject_thenCloneCopyShouldNotChange() {
Address address = new Address("Downing St 10", "London", "England");
User pm = new User("Prime", "Minister", address);
User deepCopy = (User) pm.clone();
address.setCountry("Great Britain");
assertThat(deepCopy.getAddress().getCountry())
.isNotEqualTo(pm.getAddress().getCountry());
}
6. External Libraries
6.外部程序库
The above examples look easy, but sometimes they don’t work as a solution when we can’t add an additional constructor or override the clone method.
上面的例子看起来很简单,但有时它们并不能作为一种解决方案当我们不能添加额外的构造函数或覆盖克隆方法时。
This might happen when we don’t own the code, or when the object graph is so complicated that we wouldn’t finish our project on time if we focused on writing additional constructors or implementing the clone method on all classes in the object graph.
这可能发生在我们不拥有代码的时候,或者对象图非常复杂,如果我们专注于编写额外的构造函数或在对象图的所有类上实现clone方法,我们就无法按时完成项目。
So what can we do then? In that case, we can use an external library. To achieve a deep copy, we can serialize an object and then deserialize it to a new object.
那么,我们可以做什么呢?在这种情况下,我们可以使用一个外部库。为了实现深度复制,我们可以将一个对象序列化,然后将其反序列化为一个新的对象。
Let’s look at a few examples.
让我们看看几个例子。
6.1. Apache Commons Lang
6.1.Apache Commons Lang
Apache Commons Lang has SerializationUtils#clone, which performs a deep copy when all classes in the object graph implement the Serializable interface.
Apache Commons Lang有SerializationUtils#clone,,当对象图中的所有类都实现了Serializable接口时,它会执行深度拷贝。
If the method encounters a class that isn’t serializable, it’ll fail and throw an unchecked SerializationException.
如果该方法遇到一个不可序列化的类,它就会失败,并抛出一个未检查的SerializationException。。
Consequently, we need to add the Serializable interface to our classes:
因此,我们需要在我们的类中添加 Serializable接口。
@Test
public void whenModifyingOriginalObject_thenCommonsCloneShouldNotChange() {
Address address = new Address("Downing St 10", "London", "England");
User pm = new User("Prime", "Minister", address);
User deepCopy = (User) SerializationUtils.clone(pm);
address.setCountry("Great Britain");
assertThat(deepCopy.getAddress().getCountry())
.isNotEqualTo(pm.getAddress().getCountry());
}
6.2. JSON Serialization With Gson
6.2.用Gson进行JSON序列化
The other way to serialize is to use JSON serialization. Gson is a library that’s used for converting objects into JSON and vice versa.
另一种序列化的方式是使用JSON序列化。Gson是一个库,用于将对象转换为JSON,反之亦然。
Unlike Apache Commons Lang, GSON does not need the Serializable interface to make the conversions.
与Apache Commons Lang不同,GSON不需要Serializable接口来进行转换。
Let’s have a quick look at an example:
让我们快速看一下一个例子。
@Test
public void whenModifyingOriginalObject_thenGsonCloneShouldNotChange() {
Address address = new Address("Downing St 10", "London", "England");
User pm = new User("Prime", "Minister", address);
Gson gson = new Gson();
User deepCopy = gson.fromJson(gson.toJson(pm), User.class);
address.setCountry("Great Britain");
assertThat(deepCopy.getAddress().getCountry())
.isNotEqualTo(pm.getAddress().getCountry());
}
6.3. JSON Serialization With Jackson
6.3.使用Jackson进行JSON序列化
Jackson is another library that supports JSON serialization. This implementation will be very similar to the one using Gson, but we need to add the default constructor to our classes.
Jackson是另一个支持JSON序列化的库。这个实现将与使用Gson的实现非常相似,但我们需要在我们的类中添加默认构造函数。
Let’s see an example:
让我们看一个例子。
@Test
public void whenModifyingOriginalObject_thenJacksonCopyShouldNotChange()
throws IOException {
Address address = new Address("Downing St 10", "London", "England");
User pm = new User("Prime", "Minister", address);
ObjectMapper objectMapper = new ObjectMapper();
User deepCopy = objectMapper
.readValue(objectMapper.writeValueAsString(pm), User.class);
address.setCountry("Great Britain");
assertThat(deepCopy.getAddress().getCountry())
.isNotEqualTo(pm.getAddress().getCountry());
}
7. Conclusion
7.结论
Which implementation should we use when making a deep copy? The final decision will often depend on the classes we’ll copy, and whether we own the classes in the object graph.
在进行深度拷贝时,我们应该使用哪种实现?最终的决定往往取决于我们要复制的类,以及我们在对象图中是否拥有这些类。
As always, the complete code samples for this article can be found over on GitHub.
一如既往,本文的完整代码样本可以在GitHub上找到。