首页 > 编程语言 >浅拷贝、深拷贝与序列化【初级Java必需理解的概念】

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

时间:2024-06-17 09:55:07浏览次数:15  
标签:Java name color student2 price bag 拷贝 序列化 age

浅拷贝

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

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

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=100
student2:age=18, name='张三, bag=color='红, price=100
修改了 student2 的 name 和 bag 后:
student1:age=18, name='张三, bag=color='蓝, price=200
student2:age=18, name='李四, bag=color='蓝, price=200

可以看得出,浅拷贝后:

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

@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);
    }
}

//这个测试类和之前的浅拷贝的测试类一样,但运行结果是不同的。
深拷贝后:
student1:age=18, name='张三, bag=color='红, price=100
student2:age=18, name='张三, bag=color='红, price=100
修改了 student2 的 name 和 bag 后:
student1:age=18, name='张三, bag=color='红, price=100
student2:age=18, name='李四, bag=color='蓝, price=200

不只是 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 接口

@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 类也需要实现 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=100
student2:age=18, name='张三, bag=color='红, price=100
修改了 student2 的 name 和 bag 后:
student1:age=18, name='张三, bag=color='红, price=100
student2:age=18, name='李四, bag=color='蓝, price=200

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

clone() 方法同时是一个本地(native)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。

需要注意,由于是序列化涉及到输入流和输出流的读写,在性能上要比 HotSpot 虚拟机实现的 clone() 方法差很多。

标签:Java,name,color,student2,price,bag,拷贝,序列化,age
From: https://www.cnblogs.com/GilbertDu/p/18245960

相关文章

  • Java 6.16 DeepClone and ShallowClone
    浅克隆:复制对象的引用地址,导致克隆对象和原始对象共享引用类型字段的实际对象。classPersonimplementsCloneable{Stringname;Addressaddress;publicPerson(Stringname,Addressaddress){this.name=name;this.address=add......
  • Java - function
     Java-Assignment04(100pts)InstructionsWriteeachexerciseinitsownmethod.Uncommentthefunctioncallsinmain()toactivateeachexercise.Referto"Assignment01"ifnecessary.Foreachexercise,usecommentstowritepseudocodew......
  • 面试官:Java中缓冲流真的性能很好吗?我看未必
    一、写在开头上一篇文章中,我们介绍了JavaIO流中的4个基类:InputStream、OutputStream、Reader、Writer,那么这一篇中,我们将以四个基类所衍生出来,应对不同场景的数据流进行学习。二、衍生数据流分类我们上面说了java.io包中有40多个类,都从InputStream、OutputStream、Reader、Wr......
  • 第二章JAVA的第一个开发程序
    1.JAVA的源文件以:.java结尾2.class:类,代码容器class类名Hello{  publicstaticvoidmain(String[]args){  System.out.println("Hello,world");  }}(1)main函数:被称为主函数,程序的入口,一个类中最多只能有一个主函数(2)System.out.println();//控制台打印出......
  • 5 个 JavaScript 自定义的实用函数
    嘿!......
  • Java高手的30k之路|面试宝典|精通Map篇
    HashMapHashMap是Java集合框架中非常重要的一部分,它是基于哈希表的数据结构。1.基于哈希表的实现HashMap基于哈希表实现。哈希表是通过将键(Key)映射到值(Value)的一种数据结构。具体来说,HashMap使用一个数组和链表(在冲突较少时)或红黑树(在冲突较多时)来存储元素。2.负......
  • 【Java基础】输入输出流(IO流)
     目录一、流的概念二、输入输出流的类层次结构图三、使用 InputStream 和 OutputStream流类四、使用Reader和Writer流类Java语言的输入输出功能必须借助于输入输出包java.io来实现,Java开发环境提供了丰富的流类,完成从基本的输人输出到文件操作。利用java.io......
  • JAVA 程序 在 cmd 窗口的运行
    1、在指定路径创建文件:eg文件名称:Hello.java;2、在文件内部输入内容:publicclassHello{publicstaticvoidmain(String[]args){System.out.println("Helloworld!");}}3、在cmd运行命令javac+文件名称Hello.java4、生成Hello.class......
  • Java基础:B树、B+树和红黑树的数据结构,三者区别
    B树(B-Tree)数据结构节点结构:每个节点包含多个键值和子节点指针。阶(Degree):B树的阶定义了每个节点的最小和最大键值数。对于阶为(m)的B树:每个节点最多有(m-1)个键值和(m)个子节点。每个节点(除了根节点)至少有(\lceilm/2\rceil-1)个键值和(\lceilm/......
  • 【JavaWeb】SpringBootWeb请求响应
    前言在上一次,我们开发了springbootweb的入门程序。基于SpringBoot的方式开发一个web应用,浏览器发起请求/hello后,给浏览器返回字符串“HelloWorld~”。其实呢,是我们在浏览器发起请求,请求了我们的后端web服务器(也就是内置的Tomcat)。而我们在开发web程序时呢,定义了一个控......