当前位置:首页 > 科技  > 软件

深入理解Java浅拷贝与深拷贝

来源: 责编: 时间:2024-03-18 09:39:35 274观看
导读浅拷贝和深拷贝是 Java 初中级面试中经常会被问到的一个问题,两个就像是兄弟俩,一个调皮一个乖巧,现在让我们一起来探索它们的奇妙之处!特别说明:不论是浅拷贝还是深拷贝,都可以使用Object类的clone方法来实现,代码如下:prote

浅拷贝和深拷贝是 Java 初中级面试中经常会被问到的一个问题,两个就像是兄弟俩,一个调皮一个乖巧,现在让我们一起来探索它们的奇妙之处!28H28资讯网——每日最新资讯28at.com

特别说明:不论是浅拷贝还是深拷贝,都可以使用Object类的clone方法来实现,代码如下:28H28资讯网——每日最新资讯28at.com

protected native Object clone() throws CloneNotSupportedException;

注意:clone()方法也是一个本地方法,具体实现交给虚拟机,也就是说虚拟机在运行给方法时,就会变成搞笑的C/C++代码。28H28资讯网——每日最新资讯28at.com

1. 浅拷贝

先让我们来了解一下浅拷贝。它就像是我们上学时抄了学霸一份作业,但结果可能让人出乎意料。为了演示这一点,我们创建了一个名为Student的类,这个学生有一个名为“name”的字符串字段和一个名为“age”的整数字段。28H28资讯网——每日最新资讯28at.com

public class Student implements  Cloneable{    private int age;    private String name;    public Student(int age, String name) {        this.age = age;        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return super.toString().substring(19) + "{" +                "age=" + age +                ", name='" + name + '/'' +                '}';    }    @Override    public   Object clone() throws CloneNotSupportedException {        return super.clone();    }}

接着,我们来进行测试。假设我们有一个名叫“springboot葵花宝典”的Student对象,并进行了浅拷贝。结果出来了!让我们看看会发生什么。28H28资讯网——每日最新资讯28at.com

public class CloneTest {    public static void main(String[] args) throws CloneNotSupportedException {        Student student1 = new Student(18,"springboot葵花宝典");        Student student2 = (Student) student1.clone();        System.out.println("浅拷贝后:");        System.out.println("Student1:" + student1);        System.out.println("Student2:" + student2);        student2.setName("zbbmeta");        System.out.println("调整了Student2 的 name  后:");        System.out.println("Student1:" + student1);        System.out.println("Student2:" + student2);    }}

测试结果如下:28H28资讯网——每日最新资讯28at.com

浅拷贝后:Student1:Student@4cb2c100{age=18, name='springboot葵花宝典'}Student2:Student@39fb3ab6{age=18, name='springboot葵花宝典'}调整了Student2 的 name  后:Student1:Student@4cb2c100{age=18, name='springboot葵花宝典'}Student2:Student@39fb3ab6{age=18, name='zbbmeta'}

浅拷贝后,Student1 和 Student1 引用不同对象,但值是相同的,说明拷贝成功。之后,修改了 Student2 的 name 字段,student2的name和student1的name值不同。28H28资讯网——每日最新资讯28at.com

28H28资讯网——每日最新资讯28at.com

图片图片28H28资讯网——每日最新资讯28at.com

注意:一个类没有实现 Cloneable 接口,即便它重写了 clone() 方法,依然是无法调用该方法进行对象克隆的,会抛出异常CloneNotSupportedException。28H28资讯网——每日最新资讯28at.com

Exception in thread "main" java.lang.CloneNotSupportedException: com.zbbmeta.entity.Student

28H28资讯网——每日最新资讯28at.com

思考:前面Student只有两个基本类型,没有引用类型,如果给Student添加一个自定义Book引用类型,浅拷贝会是什么结果?28H28资讯网——每日最新资讯28at.com

public class Book {    private String bookName;    private int price;    public Book(String bookName, int price) {        this.bookName = bookName;        this.price = price;    }    //...   省略getter/setter 方法      @Override    public String toString() {        return super.toString().substring(19) +                " bookName='" + bookName + '/'' +                ", price=" + price +                '}';    }}

重新编写Studnet类:28H28资讯网——每日最新资讯28at.com

public class Student implements  Cloneable{    private int age;    private String name;    private Book book;    public Student(int age, String name, Book book) {        this.age = age;        this.name = name;        this.book = book;    }   //...   省略getter/setter 方法    @Override    public String toString() {        return super.toString().substring(19) +                " age=" + age +                ", name='" + name + '/'' +                ", book=" + book +                '}';    }    @Override    public   Object clone() throws CloneNotSupportedException {        return super.clone();    }}

比之前的例子多了一个自定义类型的字段 book,clone() 方法并没有任何改变。28H28资讯网——每日最新资讯28at.com

测试类修改内容如下:28H28资讯网——每日最新资讯28at.com

public static void main(String[] args) throws CloneNotSupportedException {        Student student1 = new Student(18,"springboot葵花宝典");        Book book1 = new Book("springboot入门到精通",90);        student1.setBook(book1);        Student student2 = (Student) student1.clone();        System.out.println("浅拷贝后:");        System.out.println("Student1:" + student1);        System.out.println("Student2:" + student2);        Book book2 = student2.getBook();        book2.setBookName("K8S实战");        book2.setPrice(70);        System.out.println("调整了Student2 的 book  后:");        System.out.println("Student1:" + student1);        System.out.println("Student2:" + student2);    }

输出结果如下:28H28资讯网——每日最新资讯28at.com

浅拷贝后:Student1:Student@6fb554cc age=18, name='springboot葵花宝典', book=Book@3c09711b bookName='springboot入门到精通', price=90}}Student2:Student@3a82f6ef age=18, name='springboot葵花宝典', book=Book@3c09711b bookName='springboot入门到精通', price=90}}调整了Student2 的 book  后:Student1:Student@6fb554cc age=18, name='springboot葵花宝典', book=Book@3c09711b bookName='K8S实战', price=70}}Student2:Student@3a82f6ef age=18, name='springboot葵花宝典', book=Book@3c09711b bookName='K8S实战', price=70}}

student2.book 变更后,student1.book 也发生了改变。这是因为name字符串 String 是不可变对象,一个新的值必须在字符串常量池中开辟一段新的内存空间,而Book是自定义对象的内存地址并没有发生改变,只是对应的字段值发生了改变。28H28资讯网——每日最新资讯28at.com

28H28资讯网——每日最新资讯28at.com

28H28资讯网——每日最新资讯28at.com

总结:浅拷贝是创建一个新的对象,这个对象有着对原始对象属性值的一份精确拷贝。如果属性是基本数据类型,拷贝的就是基本数据类型的值;如果属性是引用数据类型,拷贝的就是内存地址,所以如果其中一个对象改变了引用类型的数据,就会影响另一个对象。28H28资讯网——每日最新资讯28at.com

2. 深拷贝

深拷贝和浅拷贝不同的,深拷贝中的引用类型字段也会克隆一份,当改变任何一个对象,另外一个对象不会随之改变。例子如下:28H28资讯网——每日最新资讯28at.com

public class Book implements Cloneable{    private String bookName;    private int price;    public Book(String bookName, int price) {        this.bookName = bookName;        this.price = price;    } //...   省略getter/setter 方法    @Override    public String toString() {        return super.toString().substring(19) +                " bookName='" + bookName + '/'' +                ", price=" + price +                '}';    }    @Override    public Object clone() throws CloneNotSupportedException {        return super.clone();    }}

注意:此时的 Book 类和浅拷贝时不同,重写了 clone() 方法,并实现了 Cloneable 接口。为的就是深拷贝的时候也能够克隆该字段。28H28资讯网——每日最新资讯28at.com

重新编写Studnet类:28H28资讯网——每日最新资讯28at.com

public class Student implements  Cloneable{    private int age;    private String name;    private Book book;    public Student(int age, String name, Book book) {        this.age = age;        this.name = name;        this.book = book;    }   //...   省略getter/setter 方法    @Override    public String toString() {        return super.toString().substring(19) +                " age=" + age +                ", name='" + name + '/'' +                ", book=" + book +                '}';    }    @Override    public Object clone() throws CloneNotSupportedException {        Student student = (Student) super.clone();        student.setBook((Book) student.getBook().clone());        return student;    }}

注意,此时 Student 类也与之前的不同,clone() 方法当中,不再只调用 Object 的 clone() 方法对 Student 进行克隆了,还对 Book 也进行了克隆。28H28资讯网——每日最新资讯28at.com

测试结果如下:28H28资讯网——每日最新资讯28at.com

浅拷贝后:Student1:Student@6fb554cc age=18, name='springboot葵花宝典', book=Book@3c09711b bookName='springboot入门到精通', price=90}}Student2:Student@3a82f6ef age=18, name='springboot葵花宝典', book=Book@100fc185 bookName='springboot入门到精通', price=90}}调整了Student2 的 book  后:Student1:Student@6fb554cc age=18, name='springboot葵花宝典', book=Book@3c09711b bookName='springboot入门到精通', price=90}}Student2:Student@3a82f6ef age=18, name='springboot葵花宝典', book=Book@100fc185 bookName='K8S实战', price=70}}

发现: 不仅student1 和 student2 对象不同,它们中的 book 对象不同。所以,改变了 student2 中的 book 并不会影响到 student1。28H28资讯网——每日最新资讯28at.com

28H28资讯网——每日最新资讯28at.com

图片图片28H28资讯网——每日最新资讯28at.com

思考:嵌套的对象比较多的时,每一个类都需要重写clone()方法,这样拷贝起来就比较麻烦,那么有没有别的方法实现深拷贝。28H28资讯网——每日最新资讯28at.com

利用序列化,序列化是将对象写到流中便于传输,而反序列化则是将对象从流中读取出来。写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 Serializable 接口,该接口和 Cloneable 接口类似,都是标记型接口。28H28资讯网——每日最新资讯28at.com

public class Book implements Cloneable{    private String bookName;    private int price;    public Book(String bookName, int price) {        this.bookName = bookName;        this.price = price;    } //...   省略getter/setter 方法    @Override    public String toString() {        return super.toString().substring(19) +                " bookName='" + bookName + '/'' +                ", price=" + price +                '}';    }}

注意:Book 需要实现 Serializable 接口。28H28资讯网——每日最新资讯28at.com

重新编写Studnet类:28H28资讯网——每日最新资讯28at.com

public class Student implements  Cloneable{    private int age;    private String name;    private Book book;    public Student(int age, String name, Book book) {        this.age = age;        this.name = name;        this.book = book;    }   //...   省略getter/setter 方法    @Override    public String toString() {        return super.toString().substring(19) +                " age=" + age +                ", name='" + name + '/'' +                ", book=" + book +                '}';    }    //深度拷贝    public Object deepClone() 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 接口,并且在该类中,增加了一个 deepClone() 的方法,利用 OutputStream 进行序列化,InputStream 进行反序列化,这样就实现了深拷贝。28H28资讯网——每日最新资讯28at.com

public static void main(String[] args) throws  IOException, ClassNotFoundException {        Student student1 = new Student(18,"springboot葵花宝典");        Book book1 = new Book("springboot入门到精通",90);        student1.setBook(book1);        Student student2 = (Student) student1.deepClone();        System.out.println("浅拷贝后:");        System.out.println("Student1:" + student1);        System.out.println("Student2:" + student2);        Book book2 = student2.getBook();        book2.setBookName("K8S实战");        book2.setPrice(70);        System.out.println("调整了Student2 的 book  后:");        System.out.println("Student1:" + student1);        System.out.println("Student2:" + student2);    }

与之前测试类不同的是,调用了 deepClone() 方法。28H28资讯网——每日最新资讯28at.com

测试结果如下:28H28资讯网——每日最新资讯28at.com

浅拷贝后:Student1:Student@5dfcfece age=18, name='springboot葵花宝典', book=Book@5d5eef3d bookName='springboot入门到精通', price=90}}Student2:Student@5a8e6209 age=18, name='springboot葵花宝典', book=Book@4b4523f8 bookName='springboot入门到精通', price=90}}调整了Student2 的 book  后:Student1:Student@5dfcfece age=18, name='springboot葵花宝典', book=Book@5d5eef3d bookName='springboot入门到精通', price=90}}Student2:Student@5a8e6209 age=18, name='springboot葵花宝典', book=Book@4b4523f8 bookName='K8S实战', price=70}}

测试结果和之前用 clone() 方法实现的深拷贝类似。28H28资讯网——每日最新资讯28at.com

特别说明:序列化涉及到输入流和输出流的读写,在性能上要比  虚拟机实现的 clone() 方法差很多。28H28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-76500-0.html深入理解Java浅拷贝与深拷贝

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Synchronized关键字的底层原理?

下一篇: VR在工业培训中的兴起,让明天更安全

标签:
  • 热门焦点
  • Find N3入网:最高支持16+1TB

    OPPO将于近期登场的Find N3折叠屏目前已经正式入网,型号为PHN110。本次Find N3在外观方面相比前两代有很大的变化,不再是小号的横向折叠屏,而是跟别的厂商一样采用了较为常见的
  • 对标苹果的灵动岛 华为带来实况窗功能

    继苹果的灵动岛之后,华为也在今天正式推出了“实况窗”功能。据今天鸿蒙OS 4.0的现场演示显示,华为的实况窗可以更高效的展现出实时通知,比如锁屏上就能看到外卖、打车、银行
  • 6月安卓手机性价比榜:Note 12 Turbo断层式碾压

    6月份有一个618,虽然这是京东周年庆的日子,但别的电商也都不约而同的跟进了,反正促销没坏处,厂商和用户都能满意。618期间一些产品也出现了历史低价,那么各个价位段的产品性价比
  • 5月安卓手机好评榜:魅族20 Pro夺冠

    性能榜和性价比榜之后,我们来看最后的安卓手机好评榜,数据来源安兔兔评测,收集时间2023年5月1日至5月31日,仅限国内市场。第一名:魅族20 Pro好评率:97.50%不得不感慨魅族老品牌还
  • 5月iOS设备好评榜:iPhone 14仅排第43?

    来到新的一月,安兔兔的各个榜单又重新汇总了数据,像安卓阵营的榜单都有着比较大的变动,不过iOS由于设备的更新换代并没有那么快,所以相对来说变化并不大,特别是iOS好评榜,老款设
  • 谷歌KDD'23工作:如何提升推荐系统Ranking模型训练稳定性

    谷歌在KDD 2023发表了一篇工作,探索了推荐系统ranking模型的训练稳定性问题,分析了造成训练稳定性存在问题的潜在原因,以及现有的一些提升模型稳定性方法的不足,并提出了一种新
  • JVM优化:实战OutOfMemoryError异常

    一、Java堆溢出堆内存中主要存放对象、数组等,只要不断地创建这些对象,并且保证 GC Roots 到对象之间有可达路径来避免垃 圾收集回收机制清除这些对象,当这些对象所占空间超过
  • 三星Galaxy Z Fold/Flip 5国行售价曝光 :最低7499元/12999元起

    据官方此前宣布,三星将于7月26日也就是明天在韩国首尔举办Unpacked活动,届时将带来带来包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy
  • 首发天玑9200+ iQOO Neo8系列发布首销售价2299元起

    2023年5月23日晚,iQOO Neo8系列正式发布。其中,Neo系列首款Pro之作——iQOO Neo8 Pro强悍登场,限时售价3099元起;价位段最强性能手机iQOO Neo8同期上市
Top