复制对象是企业项目中的常见操作。复制对象时,我们必须确保最终得到一个包含我们想要的值的新实例。
领域对象通常很复杂。使用根对象和组合对象制作副本也并非易事。
让我们探索使用浅复制和深复制技术来复制对象的最有效方法。
目录
要正确执行浅或深对象复制,我们首先必须知道不该做什么。 了解对象引用对于使用浅复制和深复制技术至关重要。
制作对象的副本时,避免使用相同的对象引用非常重要。正如本例所示,这是一个很容易犯的错误。首先,这是Product我们将在示例中使用的对象:
public class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public double getPrice() { return price; }
public void setName(String name) { this.name = name; }
public void setPrice(double price) { this.price = price; }
}
现在,让我们创建一个Product对象引用并将其分配给另一个变量。看似是一个副本,但实际上是同一个对象:
public static void main(String[] args) {
Product product = new Product("Macbook Pro", 3000);
Product copyOfProduct = product;
product.name = "Alienware";
System.out.println(product.name);
System.out.println(copyOfProduct.name);
}
这段代码的输出是
Alienware
Alienware
请注意,在上面的代码中,我们将对象的值分配给不同的局部变量,但该变量指向相同的对象引用。如果我们更改product或copyOfProduct对象,结果将是对原始对象的更改Product。
这是因为每次我们在Java中创建一个对象时,都会在Java的内存堆中创建一个对象引用。这使我们可以使用对象的引用变量来修改对象。
浅拷贝浅复制技术允许我们将简单的对象值复制到新对象,而不包括内部对象值。作为示例,以下是如何使用浅复制技术来复制对象Product而不使用其对象引用:
// Omitted the Product object
public class ShallowCopyPassingValues {
public static void main(String[] args) {
Product product = new Product("Macbook Pro", 3000);
Product copyOfProduct = new Product(product.getName(), product.getPrice());
product.setName("Alienware");
System.out.println(product.getName());
System.out.println(copyOfProduct.getName());
}
}
输出是
Alienware
Macbook Pro
请注意,在此代码中,当我们将值从一个对象传递到另一个对象时,会在内存堆中创建两个不同的对象。当我们更改新对象中的某个值时,原始对象中的值将保持不变。这证明对象是不同的,我们已经成功执行了浅拷贝。
注意:Builder 设计模式是执行相同操作的另一种方法。
使用 Cloneable 进行浅复制从 Java 7 开始,我们就有了CloneableJava 接口。该接口提供了另一种复制对象的方法。我们可以实现接口,Cloneable然后实现方法,而不是像我们刚才那样手动实现复制clone()逻辑。使用Cloneable和clone()方法会自动生成浅拷贝。
我不喜欢这种技术,因为它会引发检查异常,并且我们必须手动转换类类型,这使得代码变得冗长。但是,如果我们有一个具有许多属性的巨大域对象,则使用Cloneable可能会简化代码。
Cloneable如果我们在域对象中实现接口然后重写该方法,会发生以下情况clone():
public class Product implements Cloneable {
// Omitted attributes, methods and constructor
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
现在,这是再次运行的复制方法:
public class ShallowCopyWithCopyMethod {
public static void main(String[] args) throws CloneNotSupportedException {
Product product = new Product("Macbook Pro", 3000);
Product copyOfProduct = (Product) product.clone();
product.setName("Alienware");
System.out.println(product.getName());
System.out.println(copyOfProduct.getName());
}
}
正如您所看到的,复制方法非常适合制作对象的浅复制。使用它意味着我们不需要手动复制每个属性。
深拷贝深复制技术是将组合对象的值复制到另一个新对象的能力。例如,如果Product对象包含该Category对象,则预计两个对象的所有值都将复制到新对象。
Product如果该对象有一个组合对象会发生什么?浅复制技术会起作用吗?让我们看看如果我们尝试仅使用该copy()方法会发生什么。
首先,我们Product用对象组合类Order:
public class Product implements Cloneable {
// Omitted other attributes, constructor, getters and setters
private Category category;
public Category getCategory() { return category; }
}
现在,让我们使用该super.clone()方法做同样的事情:
public class TryDeepCopyWithClone {
public static void main(String[] args) throws CloneNotSupportedException {
Category category = new Category("Laptop", "Portable computers");
Product product = new Product("Macbook Pro", 3000, category);
Product copyOfProduct = (Product) product.clone();
Category copiedCategory = copyOfProduct.getCategory();
System.out.println(copiedCategory.getName());
}
}
输出是
Laptop
请注意,即使输出是“笔记本电脑”,深度复制操作也没有发生。相反,我们有相同的Category对象引用。证明如下:
public class TryDeepCopyWithClone {
public static void main(String[] args) throws CloneNotSupportedException {
// Same code as the example above
copiedCategory.setName("Phone");
System.out.println(copiedCategory.getName());
System.out.println(category.getName());
}
}
输出:
Laptop
Phone
Phone
请注意,在此代码中,当我们更改对象时并未创建副本Category。相反,只有一个对象分配给另一个变量。因此,每当我们更改引用变量时,我们都会更改在内存堆中创建的对象。
使用clone()方法进行深复制现在我们知道,clone()如果我们有一个简单的覆盖,该方法将不适用于深层复制。让我们看看如何让它发挥作用。
首先我们Cloneable在Category类中实现:
public class Category implements Cloneable {
// Omitted attributes, constructor, getters and setters
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
现在,我们必须更改Product克隆方法的实现来克隆Category对象:
public class ProductWithDeepCopy implements Cloneable {
// Omitted attributes, constructor, getters and setters
@Override
protected Object clone() throws CloneNotSupportedException {
this.category = (Category) category.clone();
return super.clone();
}
}
如果我们尝试使用与上面相同的代码示例执行深度复制,我们将获得对象值的真实副本到一个新对象中,如下所示:
public class TryDeepCopyWithClone {
public static void main(String[] args) throws CloneNotSupportedException {
Category category = new Category("Laptop", "Portable computers");
Product product = new Product("Macbook Pro", 3000, category);
Product copyOfProduct = (Product) product.clone();
Category copiedCategory = copyOfProduct.getCategory();
System.out.println(copiedCategory.getName());
copiedCategory.setName("Phone");
System.out.println(copiedCategory.getName());
System.out.println(category.getName());
}
}
输出是
Laptop
Phone
Laptop
copy()由于我们手动复制了方法中的category方法Product,所以终于可以工作了。我们将从 获取副本Product并Category使用 的copy()方法Product。
这段代码证明了深拷贝是有效的。原始对象和复制对象的值不同。因此,这不是同一个实例;它是一个复制的对象。
带序列化的浅拷贝有时需要序列化对象以将其转换为字节并通过网络传递。此操作可能很危险,因为如果未正确验证,序列化对象可能会被利用。Java 序列化的安全性超出了本文的范围,但让我们看看它如何与代码一起工作。
我们将使用上面示例中的相同类,但这一次,我们将实现该Serializable接口:
public class Product implements Serializable, Cloneable {
// Omitted attributes, constructor, getters, setters and clone method
}
请注意,只有ImplementsProduct才会被序列化。该对象不会被序列化。这是一个例子:ProductSerializableCategory
public class ShallowCopySerializable {
public static void main(String[] args) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
Product product = new Product("Macbook Pro", 3000);
out.writeObject(product);
out.flush();
out.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
Product clonedProduct = (Product) in.readObject();
in.close();
System.out.println(clonedProduct.getName());
Category clonedCategory = clonedProduct.getCategory();
System.out.println(clonedCategory);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出是
Macbook Pro
null
现在,如果我们尝试将Category对象填充到对象中Product,则会 java.io.NotSerializableException抛出异常。那是因为该Category对象没有实现Serializable.
带序列化的深复制现在,让我们看看如果我们使用与上面相同的代码但在类中添加以下内容会发生什么Category:
public class Category implements Serializable, Cloneable {
// Omitted attributes, constructor, getters, setters and clone method
// Adding toString for a good Object description
@Override
public String toString() {
return "Category{" "name='" name '\'' ", description='" description '\'' '}';
}
}
通过运行与浅可序列化复制代码相同的代码,我们也将从 中获得结果Category,并且输出应如下所示:
Macbook Pro
Category{name='Laptop', description='Portable computers'}
注意:要进一步探索 Java 序列化,请尝试此处的Java 代码挑战 。
结论有时,您只需要浅复制技术即可从表面克隆对象。但是当你想要同时复制对象及其内部对象时,你必须手动实现深复制。以下是这些重要技术的要点。
关于浅复制要记住什么Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved