这是今天我们在技术群里面讨论的一个知识点,讨论的相当激烈,由于对这一块使用的比较少,所以对这一块多少有些盲区。这篇文章总结了所讨论的内容,希望这篇文章对你有所帮助。

在 Java 开发中,对象拷贝或者说对象克隆是常有的事,对象克隆最终都离不开直接赋值浅拷贝深拷贝 这三种方式,其中直接赋值应该是我们最常用的一种方式吧,对于浅拷贝和深拷贝可能用的少,所以或多或少存在一些误区,这篇文章会详细的介绍这三种对象克隆方式。

前置知识
值类型:Java 的基本数据类型,例如 int、float
引用类型:自定义类和 Java 包装类(string、integer)

直接赋值

直接赋值是我们最常用的方式,在我们代码中的体现是Persona = new Person();Person b = a,是一种简单明了的方式,但是它只是拷贝了对象引用地址而已,并没有在内存中生成新的对象,我们可以通过下面这个例子来证明这一点

// person 对象
public class Person {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private String desc;
...省略get/set...
}
// main 方法
public class PersonApp {
public static void main(String[] args) {
// 初始化一个对象
Person person = new Person("张三",20,"123456@qq.com","我是张三");
// 复制对象
Person person1 = person;
// 改变 person1 的属性值
person1.setName("我不是张三了");
System.out.println("person对象:"+person);
System.out.println("person1对象:"+person1); }
}

运行上面代码,你会得到如下结果:

person对象:Person{name='我不是张三了', age=20, email='123456@qq.com', desc='我是张三'}
person1对象:Person{name='我不是张三了', age=20, email='123456@qq.com', desc='我是张三'}

我们将 person 对象复制给了 person1 对象,我们对 person1 对象的 name 属性进行了修改,并未修改 person 对象的name 属性值,但是我们最后发现 person 对象的 name 属性也发生了变化,其实不止这一个值,对于其他值也是一样的,所以这结果证明了我们上面的结论:直接赋值的方式没有生产新的对象,只是生新增了一个对象引用,直接赋值在 Java 内存中的模型大概是这样的

浅拷贝

浅拷贝也可以实现对象克隆,从这名字你或许可以知道,这种拷贝一定存在某种缺陷,是的,它就是存在一定的缺陷,先来看看浅拷贝的定义:如果原型对象的成员变量是值类型,将复制一份给克隆对象,也就是说在堆中拥有独立的空间;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。换句话说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。 可能你没太理解这段话,那么我们在来看看浅拷贝的通用模型:

要实现对象浅拷贝还是比较简单的,只需要被复制类需要实现 Cloneable 接口,重写 clone 方法即可,对 person 类进行改造,使其可以支持浅拷贝。

public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private String desc; /*
* 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
...省略...
}

改造很简单只需要让 person 继承 Cloneable 接口,并且重写 clone 方法即可,clone 也非常简单只需要调用 object 的 clone 方法就好,唯一需要注意的地方就是 clone 方法需要用 public 来修饰,在简单的修改 main 方法

public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("张三",20,"123456@qq.com","我是张三");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是张三的克隆对象");
// 修改 person age 的值
person1.setAge(22);
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1); }
}

重新运行 main 方法,结果如下:

person对象:Person{name='张三', age=20, email='123456@qq.com', desc='我是张三'}

person1对象:Person{name='我是张三的克隆对象', age=22, email='123456@qq.com', desc='我是张三'}

看到这个结果,你是否有所质疑呢?说好的引用对象只是拷贝了地址,为啥修改了 person1 对象的 name 属性值,person 对象没有改变?这里就是一个非常重要的知识点了,,原因在于:String、Integer 等包装类都是不可变的对象,当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址,所以在这里我们修改了 person1 对象的 name 属性值,person1 对象的 name 字段指向了内存中新的 name 对象,但是我们并没有改变 person 对象的 name 字段的指向,所以 person 对象的 name 还是指向内存中原来的 name 地址,也就没有变化

这种引用是一种特列,因为这些引用具有不可变性,并不具备通用性,所以我们就自定义一个类,来演示浅拷贝,我们定义一个 PersonDesc 类用来存放person 对象中的 desc 字段,,然后在 person 对象中引用 PersonDesc 类,具体代码如下:

// 新增 PersonDesc
public class PersonDesc {
// 描述
private String desc; }
public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 将原来的 string desc 变成了 PersonDesc 对象,这样 personDesc 就是引用类型
private PersonDesc personDesc; @Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
public Person(String name, int age, String email, String desc) {
this.name = name;
this.age = age;
this.email = email;
this.personDesc = new PersonDesc();
this.personDesc.setDesc(desc);
}
...省略...
}

修改 main 方法

public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("平头哥",20,"123456@qq.com","我的公众号是:平头哥的技术博文");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是平头哥的克隆对象");
// 修改 person age 的值
person1.setAge(22);
person1.setDesc("我已经关注了平头哥的技术博文公众号");
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1);
}
}

运行 main 方法,得到如下结果:

person对象:Person{name='平头哥', age=20, email='123456@qq.com', desc='我已经关注了平头哥的技术博文公众号'}

person1对象:Person{name='我是平头哥的克隆对象', age=22, email='123456@qq.com', desc='我已经关注了平头哥的技术博文公众号'}

我们修改 person1 的 desc 字段之后,person 的 desc 也发生了改变,这说明 person 对象和 person1 对象指向是同一个 PersonDesc 对象地址,这也符合浅拷贝引用对象只拷贝引用地址并未创建新对象的定义,到这你应该知道浅拷贝了吧。

深拷贝

深拷贝也是对象克隆的一种方式,相对于浅拷贝,深拷贝是一种完全拷贝,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象,简单点说就是拷贝对象和被拷贝对象没有任何关系,互不影响。深拷贝的通用模型如下:

深拷贝有两种方式,一种是跟浅拷贝一样实现 Cloneable 接口,另一种是实现 Serializable 接口,用序列化的方式来实现深拷贝,我们分别用这两种方式来实现深拷贝

实现 Cloneable 接口方式

实现 Cloneable 接口的方式跟浅拷贝相差不大,我们需要引用对象也实现 Cloneable 接口,具体代码改造如下:

public class PersonDesc implements Cloneable{

    // 描述
private String desc;
...省略...
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email; private PersonDesc personDesc; /**
* clone 方法不是简单的调用super的clone 就好,
*/
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
// 需要将引用对象也克隆一次
person.personDesc = (PersonDesc) personDesc.clone();
return person;
}
...省略...
}

main 方法不需要任何改动,我们再次运行 main 方法,得到如下结果:

person对象:Person{name='平头哥', age=20, email='123456@qq.com', desc='我的公众号是:平头哥的技术博文'}

person1对象:Person{name='我是平头哥的克隆对象', age=22, email='123456@qq.com', desc='我已经关注了平头哥的技术博文公众号'}

可以看出,修改 person1 的 desc 时对 person 的 desc 已经没有影响了,说明进行了深拷贝,在内存中重新生成了一个新的对象。

实现 Serializable 接口方式

实现 Serializable 接口方式也可以实现深拷贝,而且这种方式还可以解决多层克隆的问题,多层克隆就是引用类型里面又有引用类型,层层嵌套下去,用 Cloneable 方式实现还是比较麻烦的,一不小心写错了就不能实现深拷贝了,使用 Serializable 序列化的方式就需要所有的对象对实现 Serializable 接口,我们对代码进行改造,改造成序列化的方式

public class Person implements Serializable {

    private static final long serialVersionUID = 369285298572941L;
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email; private PersonDesc personDesc; public Person clone() {
Person person = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
person = (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return person;
} public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
...省略...
}
public class PersonDesc implements Serializable { private static final long serialVersionUID = 872390113109L;
// 描述
private String desc; public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} }
public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("平头哥",20,"123456@qq.com","我的公众号是:平头哥的技术博文");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是平头哥的克隆对象");
// 修改 person age 的值
person1.setAge(22);
person1.setDesc("我已经关注了平头哥的技术博文公众号");
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1);
}
}

运行 main 方法,我们可以得到跟 Cloneable 方式一样的结果,序列化的方式也实现了深拷贝。到此关于 Java 浅拷贝和深拷贝的相关内容就介绍完了,希望你有所收获。

最后

目前互联网上很多大佬都有 Java 对象克隆文章,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。

欢迎扫码关注微信公众号:「平头哥的技术博文」,和平头哥一起学习,一起进步。

Java 浅拷贝、深拷贝,你知多少?的更多相关文章

  1. Java 浅拷贝 深拷贝

    两者区别主要在于引用数据类型的属性,对于基本数据类型采用的是值传递,所以两者一样: 对于浅拷贝,引用数据类型只会进行引用传递,即复制一份引用值(内存地址)给新对象,一个对象的变化会影响到另一个的引用属 ...

  2. Java的深拷贝和浅拷贝

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

  3. Java 浅拷贝和深拷贝的理解和实现方式

    Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.举例说明:比如,对象A和对象B都属于类S,具有属性a和b.那么对对象A进行拷贝 ...

  4. java浅拷贝和深拷贝(基础也是很重要的)

    对象的copy你兴许只是懵懂,或者是并没在意,来了解下吧. 对于的github基础代码https://github.com/chywx/JavaSE 最近学习c++,跟java很是相像,在慕课网学习c ...

  5. Java基础 深拷贝浅拷贝

    Java基础 深拷贝浅拷贝 非基本数据类型 需要new新空间 class Student implements Cloneable{ private int id; private String na ...

  6. 【转】Java 浅拷贝和深拷贝的理解和实现方式

    Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.举例说明:比如,对象A和对象B都属于类S,具有属性a和b.那么对对象A进行拷贝 ...

  7. JS中有关对象的继承以及实例化、浅拷贝深拷贝的奥秘

    一.属性的归属问题 JS对象中定义的属性和方法如果不是挂在原型链上的方法和属性(直接通过如类似x的方式进行定义)都只是在该对象上,对原型链上的没有影响.对于所有实例共用的方法可直接定义在原型链上这样实 ...

  8. $.extend()浅拷贝深拷贝

    参考网址:http://bijian1013.iteye.com/blog/2255037 jQuery.extend() 函数用于将一个或多个对象的内容合并到目标对象. 注意:1. 如果只为$.ex ...

  9. Python__学习路上的坑之--引用,浅拷贝,深拷贝

    copy : 相当于只是拷贝表面一层,如果里面还有深层次的引用,那么也是直接拷贝引用的地址,而且如果拷贝对象是不可变类型比如元组,那么也是直接拷贝引用. deepcopy: 无论是拷贝可变类型还是不可 ...

随机推荐

  1. php+js实现一个简单的用户管理系统

    php + js 实现一个简单的用户管理系统 说实话,我对PHP是抵触的,但是我们的WEB课程刚好学的就是这个,不得已看了看,下面是用PHP实现的一个简单的用户管理系统. 我们首先来看一下目录结构 a ...

  2. 机器学习笔记(一)· 感知机算法 · 原理篇

    这篇学习笔记强调几何直觉,同时也注重感知机算法内部的动机.限于篇幅,这里仅仅讨论了感知机的一般情形.损失函数的引入.工作原理.关于感知机的对偶形式和核感知机,会专门写另外一篇文章.关于感知机的实现代码 ...

  3. Pytorch数据集读入——Dataset类,实现数据集打乱Shuffle

    在进行相关平台的练习过程中,由于要自己导入数据集,而导入方法在市面上五花八门,各种库都可以应用,在这个过程中我准备尝试torchvision的库dataset torchvision.datasets ...

  4. 学习笔记03http协议

    1.浏览器就是一个sokect客户端,使用http协议与服务器进行交流.http请求:请求头:(请求方法)sp(url)sp http/1.x <cr><lf>(通用头类型名) ...

  5. MySQL批量插入的分析以及注意事项

    目录 1.背景 2.两种方式对比 2.1.一次插入一条数据 2.2.一次插入多条数据 3.拓展一下 4.Other 1.背景 我们在工作中基本都会碰到批量插入数据到DB的情况,这个时候我们就需要根据不 ...

  6. Java零基础入门面向对象之多态

    多态: 多态的概念:一种事物的多种形态:允许不同类的对象对同一消息做出不同的响应 多态的前提:继承,重写:向上转型(父类引用指向子类对象) 多态的作用:提高代码的可用性:降低模块之间的耦合度 多态分类 ...

  7. 石头剪刀步(rps):dp,概率&期望

    既然已经给std了,直接扔代码啦.代码注释还是不错哒. 因为我也有点懵,不明白的或有不同见解的一定要在评论区喷我啊! #include<bits/stdc++.h> using names ...

  8. Java自动化测试框架-11 - TestNG之annotation与并发测试篇 (详细教程)

    1.简介 TestNG中用到的annotation的快速预览及其属性. 2.TestNG基本注解(注释) 注解 描述 @BeforeSuite 注解的方法只运行一次,在当前suite所有测试执行之前执 ...

  9. CDQ分治(学习笔记)

    离线算法——CDQ分治 CDQ (SHY)显然是一个人的名字,陈丹琪(MM)(NOI2008金牌女选手). 从归并开始(这里并没有从逆序对开始,是想直接引入分治思想,而不是引入处理对象) 一个很简单的 ...

  10. 大数据之路week01--自学之集合_2(列表迭代器 ListIterator)

    列表迭代器: ListIterator listerator():List集合特有的迭代器 该迭代器继承了Iterator迭代器,所以,就可以直接使用hasNext()和next()方法 特有功能: ...