在我以前的文章中,我解释了深度克隆和浅层克隆之间的区别 , 以及复制构造函数和防御性复制方法比默认的Java克隆更好。
使用复制构造函数和防御性复制方法进行的Java对象克隆当然具有某些优势,但是我们必须显式编写一些代码以通过所有这些方法实现深度克隆。 而且,仍然有可能我们会错过某些东西并且不会得到深克隆的对象。
正如在Java中创建对象的5种不同方式所讨论的那样,对序列化对象进行反序列化将创建一个状态与序列化对象相同的新对象。 因此,与上述克隆方法类似,我们也可以使用对象序列化和反序列化来实现深度克隆功能,并且通过这种方法,我们不必担心或编写用于深度克隆的代码,默认情况下会得到它。
但是,使用序列化克隆对象会带来一些性能开销,如果我们只需要克隆对象而不需要将其持久保存在文件中以备将来使用,则可以通过使用内存中序列化来改进它。
我们将使用以下Employee类作为示例,其name , 作为状态的doj和skills ,对于深度克隆,我们无需担心code> name字段,因为它是一个String对象,默认情况下所有 弦在本质上是不变的 。
您可以在《 如何在Java中创建不可变的类》以及《 为什么String是不可变的和Final》上阅读有关不可变性的更多信息。
Employee class implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
private LocalDate doj;
private List<String> skills;
public Employee(String name, LocalDate doj, List<String> skills) {
this .name = name;
this .doj = doj;
this .skills = skills;
}
public String getName() { return name; } name; }
public LocalDate getDoj() { return doj; } doj; }
public List<String> getSkills() { return skills; } skills; }
// Method to deep clone a object using in memory serialization
public Employee deepClone() throws IOException, ClassNotFoundException {
// First serializing the object and its state to memory using ByteArrayOutputStream instead of FileOutputStream.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject( this );
// And then deserializing it from memory using ByteArrayOutputStream instead of FileInputStream.
// Deserialization process will create a new object with the same state as in the serialized object,
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
return (Employee) in.readObject();
}
@Override
public String toString() {
return String.format( "Employee{name='%s', doj=%s, skills=%s}" , name, doj, skills);
}
@Override
public boolean equals(Object o) {
if ( this == o) return true ;
if (o == null || getClass() != o.getClass()) return false ;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(doj, employee.doj) &&
Objects.equals(skills, employee.skills);
}
@Override
public int hashCode() {
return Objects.hash(name, doj, skills);
} }
为了深度克隆Employee类的对象,我提供了一个 deepClone()方法,通过使用将对象序列化到内存 ByteArrayOutputStream而不是FileOutputStream并使用ByteArrayInputStream而不是FileInputStream将其反序列化。 在这里,我们将对象序列化为字节,然后再次将其从字节反序列化为对象。
Employee类实现Serializable接口来实现序列化,这有其自身的缺点,我们可以通过使用Externalizable接口自定义序列化过程来克服其中的一些缺点。
我们可以在下面的测试中运行,以了解我们的克隆方法是深层克隆还是浅层克隆,此处所有==操作将返回false(因为两个对象是分开的),而所有equals将返回true(因为两者具有相同的内容)。
public static void main(String[] args) throws IOException, ClassNotFoundException {
Employee emp = new Employee( "Naresh Joshi" , LocalDate.now(), Arrays.asList( "Java" , "Scala" , "Spring" ));
System.out.println( "Employee object: " + emp);
// Deep cloning `emp` object by using our `deepClone` method.
Employee clonedEmp = emp.deepClone();
System.out.println( "Cloned employee object: " + clonedEmp);
System.out.println();
// All of this will print false because both objects are separate.
System.out.println(emp == clonedEmp);
System.out.println(emp.getDoj() == clonedEmp.getDoj());
System.out.println(emp.getSkills() == clonedEmp.getSkills());
System.out.println();
// All of this will print true because `clonedEmp` is a deep clone of `emp` and both have the same content.
System.out.println(Objects.equals(emp, clonedEmp));
System.out.println(Objects.equals(emp.getDoj(), clonedEmp.getDoj()));
System.out.println(Objects.equals(emp.getSkills(), clonedEmp.getSkills())); }
我们知道反序列化过程每次都会创建一个新对象,如果我们必须使我们的类单身,那将是不好的。 这就是为什么我们需要重写和禁用单例类的序列化,这可以通过提供writeReplace和readResolve方法来实现。
与序列化类似,Java克隆也不能与单例模式一起使用,这就是为什么我们也需要覆盖和禁用它。 我们可以通过实现克隆的方式来做到这一点,以便它要么抛出 CloneNotSupportedException或每次都返回相同的实例。
您可以在Java Cloning和Java上阅读有关Java克隆和序列化的更多信息。 Java序列化主题。
您可以在此找到本文的完整源代码。
Github存储库 ,请随时提供宝贵的反馈。