写点什么

浅拷贝、深拷贝与序列化【初级 Java 必需理解的概念】

  • 2024-06-18
    福建
  • 本文字数:3976 字

    阅读完需:约 13 分钟

浅拷贝


首先创建两个类,方便理解浅拷贝

@Dataclass 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(); }}
复制代码


@Dataclass 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 接口只是一个标记接口(没属性和方法):

public interface Cloneable {}
复制代码


标记接口的作用其实很简单,用来表示某个功能在执行的时候是合法的。


如果不实现 Cloneable 接口直接重写并调用 clone()方法,会抛出 CloneNotSupportedException 异常。


测试类

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);
}}
//打印结果浅拷贝后:student1:age=18, name='张三, bag=color='红, price=100student2:age=18, name='张三, bag=color='红, price=100修改了 student2 的 name 和 bag 后:student1:age=18, name='张三, bag=color='蓝, price=200student2:age=18, name='李四, bag=color='蓝, price=200
复制代码


可以看得出,浅拷贝后:


修改了 student2 的非引用类型属性 name,student1 的 name 并不会跟着改变


但修改了 student2 的引用类型属性 bag,student1 的 bag 跟着改变了


说明浅拷贝克隆的对象中,引用类型的字段指向的是同一个,当改变任何一个对象,另外一个对象也会随之改变。


深拷贝


深拷贝和浅拷贝不同的,深拷贝中的引用类型字段也会克隆一份,当改变任何一个对象,另外一个对象不会随之改变。


例子

@Dataclass 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 接口。为的就是深拷贝的时候也能够克隆该字段。

@Dataclass 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); }}
//这个测试类和之前的浅拷贝的测试类一样,但运行结果是不同的。深拷贝后:student1:age=18, name='张三, bag=color='红, price=100student2:age=18, name='张三, bag=color='红, price=100修改了 student2 的 name 和 bag 后:student1:age=18, name='张三, bag=color='红, price=100student2:age=18, name='李四, bag=color='蓝, price=200
复制代码


不只是 student1 和 student2 是不同的对象,它们中的 bag 也是不同的对象。所以,改变了 student2 中的 bag 并不会影响到 student1。


不过,通过 clone() 方法实现的深拷贝比较笨重,因为要将所有的引用类型都重写 clone() 方法。


更好的方法是利用序列化


序列化


序列化是将对象写入流中,而反序列化是将对象从流中读取出来。写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 Serializable 接口,该接口和 Cloneable 接口类似,都是标记型接口。


来看例子

@Dataclass 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 接口

@Dataclass 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 类也需要实现 Serializable 接口,并且在该类中,增加了一个 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);
}}

//与之前测试类不同的是,调用了 serializeClone() 方法。浅拷贝后:student1:age=18, name='张三, bag=color='红, price=100student2:age=18, name='张三, bag=color='红, price=100修改了 student2 的 name 和 bag 后:student1:age=18, name='张三, bag=color='红, price=100student2:age=18, name='李四, bag=color='蓝, price=200
复制代码


测试结果和之前用 clone() 方法实现的深拷贝一样。


文章转载自:救苦救难韩天尊

原文链接:https://www.cnblogs.com/GilbertDu/p/18245960

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
浅拷贝、深拷贝与序列化【初级Java必需理解的概念】_Java_快乐非自愿限量之名_InfoQ写作社区