概述
在java的应用中,我们不免的会需要获得一个完全一模一样的对象,在操作新对象的时候不希望旧的对象被改变,如果直接加一个对相同对象的引用,那么修改新对象的时候实际上修改的是与原对象相同的内存空间,达不到我们的目的。
此时我们就需要对对象进行克隆,而关于对象的克隆,就有浅克隆和深克隆两种,浅克隆仅能满足简单的对象克隆,而较为复杂的对象,就需要用深克隆才能实现了。
1.浅克隆——Cloneable接口
浅克隆需要对象实现Cloneable接口并重写clone方法
方法体通常只需要retun (E) super.clone();即可
但这种方式如果碰到对象是复杂的引用类型,则会因复制过去的是该对象的地址造成问题
这意味着复制出来的对象和原对象共享一个对象(类似于static变量)
(1).浅克隆有关引用类型和String类
很多资料上会直接谈到浅克隆中一旦有引用型变量就不行,那我前面为什么要提到"复杂的"呢,这是因为像String这种类型的对象它虽然是引用类型,但却不会因为浅复制而受到影响,因为String类是不可变的,一旦修改String类对象的值,那么底层就相当于重新创建了一个String对象重新赋值引用,所以我们可以认为:
String这种不可变对象天然地具有浅克隆的特性。
(2).有可变类的情况怎么办
public Employee clone() throws CloneNotSupportedException
{
Employee cloned = (Employee) super.clone();
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
我们可以通过将可变的引用类型字段也实现Cloneable接口,并同时实现自己的clone()方法,然后再在其超类中调用该克隆方法去覆盖原有的指针指向。
2.深克隆——序列化
深复制需要对象实现Serializable接口,这里的深复制涉及到序列化的知识点
不用重写或者实现该接口的任何方法,实现这个接口仅表示该对象可以被序列化
注意:被关键字transient修饰的属性表示不想被序列化,序列化时将被忽略,数据将不存在
案例中使用字节流和对象流来实现深克隆,具体步骤在代码中以注释形式展现
3.案例代码
import java.io.*;
class Person implements Serializable,Cloneable {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Object deepClone() throws IOException, ClassNotFoundException {
//创建一个 ByteArrayOutputStream 对象 baos,用于在内存中创建字节数组缓冲区。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//创建一个 ObjectOutputStream 对象 oos,将其与 baos 关联,用于将对象写入字节流。
ObjectOutputStream oos = new ObjectOutputStream(baos);
//将原对象写入 baos 中,将其转换为字节流并存储在字节数组缓冲区中。
oos.writeObject(this);
//调用 close() 关闭输出流,释放与输出流关联的资源,并确保数据已写入字节数组缓冲区。
oos.close();
//创建一个 ByteArrayInputStream 对象 bais,将其初始化为字节数组缓冲区的内容。
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
//创建一个 ObjectInputStream 对象 ois,将其与 bais 关联,用于从字节流中读取对象。
ObjectInputStream ois = new ObjectInputStream(bais);
//从 bais 中读取字节流,并将其反序列化为对象。
Object obj = ois.readObject();
//调用 close() 关闭输入流,释放与输入流关联的资源。
ois.close();
return obj;
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
class Address implements Serializable {
private String city;
public Address(String city) {
this.city = city;
}
public void setCity(String city) {
this.city = city;
}
public String getCity() {
return city;
}
}
public class CloneTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
Address address = new Address("New York");
Person person1 = new Person("John", address);
//使用深克隆方法复制生成对象person2
Person person2 = (Person) person1.deepClone();
System.out.println(person1.getName()); // 输出: John
System.out.println(person1.getAddress().getCity()); // 输出: New York
System.out.println(person2.getName()); // 输出: John
System.out.println(person2.getAddress().getCity()); // 输出: New York
// 修改 person2 的 city
person2.getAddress().setCity("London");
// 由于深复制,person1 和 person2 拥有各自独立的 Address 对象,修改其中一个的地址不会影响另一个
System.out.println(person1.getAddress().getCity()); // 输出: New York
System.out.println(person2.getAddress().getCity()+"\n"); // 输出: London
//使用浅克隆方法复制生成对象person3
Person person3 = person1.clone();
// 修改 person3 的 city
person3.getAddress().setCity("London");
// 由于浅复制,person1 和 person3 拥有相同的 Address 对象,修改其中一个的地址会直接影响另一个
System.out.println(person1.getAddress().getCity()); // 输出: London
System.out.println(person2.getAddress().getCity()); // 输出: London
}
}
4.经验总结
这里使用通俗易懂的语言再次解析序列化和反序列化的过程:
1.首先我们在内存上创建通用的一个字节输出流,用于接收各种类型的流式数据;
2.我们再创建一个对象输出流去绑定前面创建的字节输出流,确定数据具体类型为对象;
3.将原对象以对象流的形式写入其绑定的字节流内存空间;
4.释放对象输出流,确保内容写入字节流
5.开始反序列化,创建一个字节输入流,绑定到字节输出流的位置上
6.创建一个对象输入流,绑定到刚才创建的字节输入流上,确保是以对象形式读取这段数据;
7.从中以Object类型读取对象数据
8.关闭对象输入流,确保内容全部写出