我们提供安全,免费的手游软件下载!
浅拷贝是指在拷贝对象时,如果对象中包含引用类型的字段,那么拷贝后的对象中的引用类型字段指向的是同一个对象。相比之下,深拷贝则是在拷贝对象时,会将对象中的引用类型字段也进行拷贝,使得拷贝后的对象中的引用类型字段指向的是不同的对象。
为了更好地理解浅拷贝和深拷贝,首先创建两个类,分别是Student和Bag。
@Data
class Student implements Cloneable{
//年龄和名字是基本属性
private int age;
private String name;
//书包是引用属性
private Bag bag;
public Student(int age, String name, Bag bag) {
this.age = age;
this.name = name;
this.bag = bag;
}
@Override
public String toString() {
return "age=" + age + ", name='" + name + ", bag=" + bag;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Student类中包含了一个引用类型字段Bag,而Bag类如下所示:
@Data
class Bag {
private String color;
private int price;
public Bag(String color, int price) {
this.color = color;
this.price = price;
}
@Override
public String toString() {
return "color='" + color + ", price=" + price;
}
}
接下来,我们来看一下Cloneable接口。Cloneable接口是一个标记接口,它并没有包含任何属性和方法。它的作用是用来表示某个功能在执行的时候是合法的。如果不实现Cloneable接口直接重写并调用clone()方法,会抛出CloneNotSupportedException异常。
下面是一个测试类TestClone,用于测试浅拷贝的效果:
class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student(18, "张三", new Bag("红",100));
Student student2 = (Student) student1.clone();
System.out.println("浅拷贝后:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
//修改非引用类型属性name
student2.setName("李四");
//修改引用类型属性bag
Bag bag = student2.getBag();
bag.setColor("蓝");
bag.setPrice(200);
System.out.println("修改了 student2 的 name 和 bag 后:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
运行结果表明,浅拷贝后,修改了student2的非引用类型属性name,student1的name并不会跟着改变,但修改了student2的引用类型属性bag,student1的bag跟着改变了。这说明浅拷贝克隆的对象中,引用类型的字段指向的是同一个对象,当改变任何一个对象,另外一个对象也会随之改变。
接下来,我们来看一下深拷贝。深拷贝和浅拷贝的区别在于,深拷贝中的引用类型字段也会克隆一份,当改变任何一个对象,另外一个对象不会随之改变。下面是一个例子:
@Data
class Bag implements Cloneable {
private String color;
private int price;
public Bag(String color, int price) {
this.color = color;
this.price = price;
}
@Override
public String toString() {
return "color='" + color + ", price=" + price;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
在Bag类中,我们重写了clone()方法,并实现了Cloneable接口,以便在深拷贝时也能够克隆该字段。接下来是Student类的修改:
@Data
class Student implements Cloneable{
//年龄和名字是基本属性
private int age;
private String name;
//书包是引用属性
private Bag bag;
public Student(int age, String name, Bag bag) {
this.age = age;
this.name = name;
this.bag = bag;
}
@Override
public String toString() {
return "age=" + age + ", name='" + name + ", bag=" + bag;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student s = (Student) super.clone();
s.setBag((Bag) s.getBag().clone());
return s;
}
}
在Student类中,clone()方法不再只调用Object的clone()方法对Student进行克隆,还对Bag也进行了克隆。接下来是一个深拷贝的测试类:
class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student(18, "张三", new Bag("红",100));
Student student2 = (Student) student1.clone();
System.out.println("深拷贝后:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
//修改非引用类型属性name
student2.setName("李四");
//修改引用类型属性bag
Bag bag = student2.getBag();
bag.setColor("蓝");
bag.setPrice(200);
System.out.println("修改了 student2 的 name 和 bag 后:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
测试结果表明,通过clone()方法实现的深拷贝,student1和student2是不同的对象,它们中的bag也是不同的对象。所以,改变了student2中的bag并不会影响到student1。
然而,通过clone()方法实现的深拷贝比较笨重,因为要将所有的引用类型都重写clone()方法。更好的方法是利用序列化。
序列化是将对象写入流中,而反序列化是将对象从流中读取出来。写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现Serializable接口,该接口和Cloneable接口类似,都是标记型接口。
下面是一个例子:
@Data
class Bag implements Serializable {
private String color;
private int price;
public Bag(String color, int price) {
this.color = color;
this.price = price;
}
@Override
public String toString() {
return "color='" + color + ", price=" + price;
}
}
Bag类需要实现Serializable接口。接下来是Student类的修改:
@Data
class Student implements Serializable {
//年龄和名字是基本属性
private int age;
private String name;
//书包是引用属性
private Bag bag;
public Student(int age, String name, Bag bag) {
this.age = age;
this.name = name;
this.bag = bag;
}
@Override
public String toString() {
return "age=" + age + ", name='" + name + ", bag=" + bag;
}
//使用序列化拷贝
public Object serializeClone() throws IOException, ClassNotFoundException {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
在Student类中,增加了一个serializeClone()的方法,利用OutputStream进行序列化,InputStream进行反序列化,从而实现深拷贝。接下来是一个序列化拷贝的测试类:
class TestClone {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Student student1 = new Student(18, "张三", new Bag("红",100));
Student student2 = (Student) student1.serializeClone();
System.out.println("序列化拷贝后:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
//修改非引用类型属性name
student2.setName("李四");
//修改引用类型属性bag
Bag bag = student2.getBag();
bag.setColor("蓝");
bag.setPrice(200);
System.out.println("修改了 student2 的 name 和 bag 后:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
测试结果表明,序列化拷贝的效果与通过clone()方法实现的深拷贝一样。需要注意的是,由于序列化涉及到输入流和输出流的读写,在性能上要比HotSpot虚拟机实现的clone()方法差很多。
热门资讯