1、介绍

关于Java的深拷贝和浅拷贝,简单来说就是创建一个和已知对象一模一样的对象。可能日常编码过程中用的不多,但是这是一个面试经常会问的问题,而且了解深拷贝和浅拷贝的原理,对于Java中的所谓值传递或者引用传递将会有更深的理解。

2、浅拷贝

浅拷贝就是获得拷贝对象的引用,而不是正真意义上的拷贝一个对象,例如

A a = new A();

A b = a;

此时引用变量a和b 同时指向了同一个堆中的内存空间,变量b只是复制了实例A的引用地址,并不是重新在堆中开辟了一个新的空间位置,来完整的复制实例A 如图

3、深拷贝

深拷贝则是拷贝了源对象的所有值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。深拷贝则是真正意义上的拷贝,如图

4、深拷贝和浅拷贝的区别

简单来说就是一句话: 深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

5、浅拷贝的实现

首先,我们定义一下需要拷贝的简单对象。

public class Student{

private String name;

private int age;

private String sex;

}

public class School {

private String schoolName;

private int stuNums;

private Student stu;

}

如上述代码,我们定义了一个Student学生类,包含name姓名,和age年龄,sex性别,而是另一个School类,包含schoolName学校名称和stuNums学生数量以及Student学生,其中Student并不是字符串,而是一个Student类。接下来我们将详细描述如何签拷贝School对象。

我们看如下这段代码:

public class Student{

private String name;

private int age;

private String sex;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public String getSex() {

return sex;

}

public void setSex(String sex) {

this.sex = sex;

}

@Override

public String toString() {

return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";

}

}

public class School implements Cloneable{

private String schoolName;

private int stuNums;

private Student stu;

public String getSchoolName() {

return schoolName;

}

public void setSchoolName(String schoolName) {

this.schoolName = schoolName;

}

public int getStuNums() {

return stuNums;

}

public void setStuNums(int stuNums) {

this.stuNums = stuNums;

}

public Student getStu() {

return stu;

}

public void setStu(Student stu) {

this.stu = stu;

}

@Override

protected School clone() throws CloneNotSupportedException {

// TODO Auto-generated method stub

return (School)super.clone();

}

@Override

public String toString() {

return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";

}

}

这是一个我们要进行赋值的原始类 School。下面我们产生一个 School对象,并调用其 clone 方法复制一个新的对象。

注意:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法。

public class TestClone {

public static void main(String[] args) throws CloneNotSupportedException {

//创建初始的School对象

School s1 = new School();

s1.setSchoolName("xx大学");

s1.setStuNums(2000);

Student stu1 = new Student();

stu1.setAge(20);

stu1.setName("肉丁");

stu1.setSex("女");

s1.setStu(stu1);

School s2 = s1.clone(); //调用重写的clone方法,clone出一个新的school---s2

System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode());

System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());//System.out.println(s1.getStu().getAge()==s2.getStu().getAge());

System.out.println("----------------------------");

System.out.println("修改克隆出来的对象");

Student student2 = s2.getStu();

student2.setAge(21);

student2.setName("斌");

s2.setStu(student2);

System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode());

System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());//System.out.println(s1.getStu().getAge()==s2.getStu().getAge());

}

}

我们查看输出的结果

s1: School [schoolName=xx大学, stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]

s1的hashcode:500977346

s1中stu1的hashcode:20132171

s2: School [schoolName=xx大学, stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]

s2的hashcode:186370029

s2中stu1的hashcode:20132171

修改克隆出来的对象

s1: School [schoolName=xx大学, stuNums=2000, stu=Student [name=斌, age=21, sex=女]]

s1的hashcode:500977346

s1中stu1的hashcode:20132171

s2: School [schoolName=xx大学, stuNums=2000, stu=Student [name=斌, age=21, sex=女]]

s2的hashcode:186370029

s2中stu1的hashcode:20132171

首先看原始类 School 实现 Cloneable 接口,并且覆写 clone 方法,它还有三个属性,一个引用类型 String定义的 schoolName,一个基本类型 int定义的 stuNums,还有一个引用类型 Student,这是一个自定义类,这个类也包含三个属性 name、age和 sex。

接着看测试内容,首先我们创建一个School类的对象s1 ,其schoolName为xx大学,stuNums为2000,学生类Stundet三个属性为 20、肉丁和女。接着我们调用 clone() 方法复制另一个对象 s2,接着打印这两个对象的内容。

从第 2 行和第 5 行打印结果:

s1的hashcode:500977346

s2的hashcode:186370029

可以看出这是两个不同的对象。

从第 1 行和第 4 行打印的对象内容看,原对象 s1 和克隆出来的对象 s2 内容完全相同。

代码中我们只是更改了克隆对象 s2 的属性Student 为斌、21、女(原对象 s1 是肉丁、20、女) ,但是从第 8 行和第 11 行打印结果来看,原对象 s1 和克隆对象 s2 的 Student属性都被修改了。

也就是说对象 School的属性 Student,经过 clone 之后,其实只是复制了其引用,他们指向的还是同一块堆内存空间,当修改其中一个对象的属性 Student,另一个也会跟着变化。

6、深拷贝的实现

深拷贝的方式有很多种,文中我们将介绍三种方式

方法一 构造函数

方法二 重载clone()方法

方法三Serializable序列化

6.1、构造函数

public void constructorCopy() {

Student student = new Student ("小李",21,"男");

School school = new School ("xx大学",100, student);

// 调用构造函数时进行深拷贝

School copySchool = new School (school.getSchoolName(),school.getStuNums(), new Student(student.getName(), student.getAge(),student.getSex()));

// 修改源对象的值

copySchool .getStudent().setSex("女");

// 检查两个对象的值不同

System.out.println(school.hashCode()==school2.hasCode())

}

6.2、重载clone()方法

Object父类有个clone()的拷贝方法,不过它是protected类型的,我们需要重写它并修改为public类型。除此之外,子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。让我们还是看之前的School代码

public class School implements Cloneable{

private String schoolName;

private int stuNums;

private Student stu;

public String getSchoolName() {

return schoolName;

}

public void setSchoolName(String schoolName) {

this.schoolName = schoolName;

}

public int getStuNums() {

return stuNums;

}

public void setStuNums(int stuNums) {

this.stuNums = stuNums;

}

public Student getStu() {

return stu;

}

public void setStu(Student stu) {

this.stu = stu;

}

@Override

protected School clone() throws CloneNotSupportedException {

School school = (School) super.clone();

school.stu = (Student) stu.clone();

return school;

}

@Override

public String toString() {

return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";

}

}

public class Student implements Cloneable{

private String name;

private int age;

private String sex;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public String getSex() {

return sex;

}

public void setSex(String sex) {

this.sex = sex;

}

@Override

public String toString() {

return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";

}

@Override

protected Student clone() throws CloneNotSupportedException {

return (Student)super.clone();

}

}

我们查看输出的结果

s1: School [schoolName=xx大学, stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]

s1的hashcode:500977346

s1中stu1的hashcode:20132171

s2: School [schoolName=xx大学, stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]

s2的hashcode:186370029

s2中stu1的hashcode:2094548358

修改克隆出来的对象

s1: School [schoolName=xx大学, stuNums=2000, stu=Student [name=肉丁, age=20, sex=女]]

s1的hashcode:500977346

s1中stu1的hashcode:20132171

s2: School [schoolName=xx大学, stuNums=2000, stu=Student [name=斌, age=21, sex=女]]

s2的hashcode:186370029

s2中stu1的hashcode:2094548358

需要注意的是,super.clone()其实是浅拷贝,所以在重写School类的clone()方法时,Student对象需要调用stu.clone()重新赋值。

查看第 2 行和第 5 行

s1的hashcode:500977346

s2的hashcode:186370029

查看第 3 行和第 6 行

s1中stu1的hashcode:20132171

s2中stu1的hashcode:2094548358

通过结果发现重新复制的对象s2和s1的hashCode不同,并且s1.stu与s2.stu2的hashCode也不同,由此证明复制的新的对象和原本的对象指向的不是同一个一个对象,意味着堆内存中存在两个School实例

6.3、Serializable序列化

我们看如下的代码

import java.io.Serializable;

public class User implements Serializable {

private String name;

private Address2 address;

public User(String name, Address2 address) {

this.name = name;

this.address = address;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Address2 getAddress() {

return address;

}

public void setAddress(Address2 address) {

this.address = address;

}

public Object deepClone() throws Exception

{

// 序列化

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();

}

}

import java.io.Serializable;

public class Address2 implements Serializable {

private String city;

private String country;

public Address2(String city, String country) {

this.city = city;

this.country = country;

}

public String getCity() {

return city;

}

public void setCity(String city) {

this.city = city;

}

public String getCountry() {

return country;

}

public void setCountry(String country) {

this.country = country;

}

@Override

public String toString() {

return "Address2{" +

"city='" + city + '\'' +

", country='" + country + '\'' +

'}';

}

}

注意 要使用序列化的方式来复制对象 对象需要继承Serializable接口,接下来我们查看测试类

public static void main(String[] args) throws Exception {

Address2 address = new Address2("大同", "中国");

User user = new User("yznl", address);

User user2 = (User) user.deepClone();

System.out.println(user.toString());

System.out.println(user2.toString());

}

结果如下:

277630005,1915503092

通过比较user对象和克隆的user2对象的hashCode发现,也是不同的对象

到此这篇关于Java的深拷贝与浅拷贝的几种实现方式的文章就介绍到这了,更多相关Java 深拷贝与浅拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

java拷贝函数_Java的深拷贝与浅拷贝的几种实现方式相关推荐

  1. java deep clone util_Java Clone深拷贝与浅拷贝的两种实现方法

    1.首先,你要知道怎么实现克隆:实现Cloneable接口,在bean里面重写clone()方法,权限为public. 2.其次,你要大概知道什么是地址传递,什么是值传递. 3.最后,你要知道你为什么 ...

  2. java 复制Map对象(深拷贝与浅拷贝)

    java 复制Map对象(深拷贝与浅拷贝) CreationTime--2018年6月4日10点00分 Author:Marydon 1.深拷贝与浅拷贝 浅拷贝:只复制对象的引用,两个引用仍然指向同一 ...

  3. 浅拷贝的五种实现方式

    浅拷贝的五种实现方式 浅拷贝与深拷贝是一道经久不衰的面试题,今日先总结一下有哪些浅拷贝的方式,以及各自的优缺点 首先,看一下什么是浅拷贝 自己创建一个新的对象,来接受你要重新复制或引用的对象值.如果对 ...

  4. vue 深度拷贝数组_前端深拷贝和浅拷贝

    在前端攻城狮的工作实际应用中,有很多情况下在处理数据的时候,会用到数据的深拷贝和浅拷贝 例如:vue中数据是双向绑定的,页面显示依赖于从后台获取到的数据,但要将这个数据当做参数发送给另外一个接口的时候 ...

  5. 详解Java中的clone方法 -- 深拷贝和浅拷贝

    Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...

  6. java面试常见题:深拷贝与浅拷贝

    深拷贝与浅拷贝的区别 浅拷贝只是拷贝了源对象的地址,所以当源对象发生改变时,拷贝的对象的值也会对应发生改变. 深拷贝则是拷贝了源对象的所有值,而不是地址,所以深拷贝对象中的值不会随着源对象中的值的改变 ...

  7. java lambda函数_Java SE 8新功能介绍:使用Lambda Expression进行函数式编程

    java lambda函数 " Java SE 8新功能浏览 "系列的这篇文章将深入了解Lambda表达式 . 我将向您展示Lambda表达式的几种不同用法. 它们都具有功能接口的 ...

  8. java 字符串函数_Java字符串函数– 25+必须知道方法

    java 字符串函数 Java字符串函数 (Java String Functions) Java String class has a lot of functions to manipulate ...

  9. java虚拟函数_java实现多态中的虚函数相关概念

    本文转载自参考博客 1. Java虚函数 虚函数的存在是为了多态. C++中普通成员函数加上virtual关键字就成为虚函数 Java中其实没有虚函数的概念,它的普通函数就相当于C++的虚函数,动态绑 ...

最新文章

  1. Spring解析,加载及实例化Bean的顺序(零配置)
  2. jQuery插件开发学习笔记
  3. 如何选择合适的NoSQL数据库
  4. Spring读取jar包外部的配置文件properties
  5. are exo exo是什么歌 we_are exo exo是什么歌 we_EXO we are one
  6. sqlyog软件的使用
  7. 【商业】10个经济学概念,让你看清复杂世界
  8. led计数电路实验报告_LED点亮程序
  9. 【面经】记一次字节跳动后端面试经历
  10. Tensorflow模型通过ckpt获取参数
  11. 该来的终于来了:“第一起”基于 IPv6 的 DDoS 攻击
  12. Oracle中查看所有的表,用户表,列名,主键,外键
  13. 三维可视化JavaScript组件接口
  14. 淘宝人生3周年,聊聊我们3D互动游戏的项目经验
  15. 大类资产配置(二)风险预算及风险平价模型
  16. Android涂鸦框架Doodle——仿微信图片编辑(多功能画板)
  17. Web 应用程序防火墙 (WAF)
  18. tomcat同时部署两个项目的问题
  19. 大数据培训技术phoenix表操作
  20. 晶振01——晶振分类和无源晶振的设计

热门文章

  1. 数据分析师经常遇到的13个问题
  2. 解决 PowerDesigner 错误 The generation has been cancelled…
  3. 运用扩展管理器完成flash图层的合并
  4. QT+SQLite 判断表中是否含有指定字段
  5. 信息收集 ——情报分析
  6. SQL注入——SQLmap的进阶使用(十三)
  7. 鸿蒙系统适配机型_华为鸿蒙 OS 适配机型曝光,除了 Mate 40 还有这几款!
  8. python数组随机打乱_对Python random模块打乱数组顺序的实例讲解
  9. Android Studio开发版(debug)和发布版(release)获取SHA1和MD5和SHA256的最原始方法
  10. 服务器设备日常维护与管理论文,浅谈设备管理与维护论文