Java的深拷贝与浅拷贝的几种实现方式

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 深拷贝与浅拷贝内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java 深拷贝与浅拷贝的分析

    在正式的进入主题之前,我们先来了解下深拷贝和前拷贝的概念: 浅拷贝: 会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的是基本类型的值:如果属性是内存地址,拷贝的就是内存地址,因此如果一个对象改变了这个地址就会影响到另一个对象: 深拷贝: 不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值: 了解完概念之后,我们来测试下普通的对象赋值操作属于深拷贝还是浅拷贝: 测试代码: public class Depth

  • Java Clone深拷贝与浅拷贝的两种实现方法

    1.首先,你要知道怎么实现克隆:实现Cloneable接口,在bean里面重写clone()方法,权限为public. 2.其次,你要大概知道什么是地址传递,什么是值传递. 3.最后,你要知道你为什么使用这个clone方法. 先看第一条,简单的克隆代码的实现.这个也就是我们在没了解清楚这个Java的clone的时候,会出现的问题. 看完代码,我再说明这个时候的问题. 先看我要克隆的学生bean的代码: package com.lxk.model; /** * 学生类:有2个属性:1,基本属性-S

  • java 深拷贝与浅拷贝机制详解

     java 深拷贝与浅拷贝机制详解 概要: 在Java中,拷贝分为深拷贝和浅拷贝两种.java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝. (一)Object中clone方法 如果我们new出一个新对象,用一个声明去引用它,之后又用另一个声明去引用前一个声明,那么最后的结果是:这两个声明的变量将指向同一个对象,一处被改全部被改.如果我们想创建一个对象的copy,这个copy和对象的各种属性完全相同,而且修

  • 详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    1.序列化实现 public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T object){ T cloneObj = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new Objec

  • Java中的深拷贝和浅拷贝介绍

    一.引言   对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去.在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部 数据.Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy).深拷贝(Deep Copy).延迟拷贝(Lazy Copy). 二.浅拷贝 1.什么是浅拷贝   浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝.如果属性是基本类型,拷贝的就是基本类型的值:如果属性是内存地

  • Java中的深拷贝(深复制)和浅拷贝(浅复制)介绍

    深拷贝(深复制)和浅拷贝(浅复制)是两个比较通用的概念,尤其在C++语言中,若不弄懂,则会在delete的时候出问题,但是我们在这幸好用的是Java.虽然java自动管理对象的回收,但对于深拷贝(深复制)和浅拷贝(浅复制),我们还是要给予足够的重视,因为有时这两个概念往往会给我们带来不小的困惑. 浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象.深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象.举例来说更加清楚:对象A1中包含对B1的引用

  • Java的深拷贝与浅拷贝的几种实现方式

    1.介绍 关于Java的深拷贝和浅拷贝,简单来说就是创建一个和已知对象一模一样的对象.可能日常编码过程中用的不多,但是这是一个面试经常会问的问题,而且了解深拷贝和浅拷贝的原理,对于Java中的所谓值传递或者引用传递将会有更深的理解. 2.浅拷贝 浅拷贝就是获得拷贝对象的引用,而不是正真意义上的拷贝一个对象,例如 A a = new A(); A b = a; 此时引用变量a和b 同时指向了同一个堆中的内存空间,变量b只是复制了实例A的引用地址,并不是重新在堆中开辟了一个新的空间位置,来完整的复制

  • Java的深拷贝和浅拷贝深入了解

    关于Java的深拷贝和浅拷贝,简单来说就是创建一个和已知对象一模一样的对象.可能日常编码过程中用的不多,但是这是一个面试经常会问的问题,而且了解深拷贝和浅拷贝的原理,对于Java中的所谓值传递或者引用传递将会有更深的理解. 1.创建对象的5种方式 ①.通过 new 关键字 这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象.比如 Object obj = new Object(); ②.通过 Class 类的 newInstance() 方法 这种默认是调用类的无参构

  • java中进程与线程_三种实现方式总结(必看篇)

    一:进程与线程 概述:几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程.当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是一个线程. 进程:进程是指处于运行过程中的程序,并且具有一定的独立功能.进程是系统进行资源分配和调度的一个单位.当程序进入内存运行时,即为进程. 进程的三个特点: 1:独立性:进程是系统中独立存在的实体,它可以独立拥有资源,每一个进程都有自己独立的地址空间,没有进程本身的运行,用户进程不可以直接访问其他进程的地址空间. 2:

  • Java实现Map集合遍历的四种常见方式与用法分析

    本文实例讲述了Java实现Map集合遍历的四种常见方式与用法.分享给大家供大家参考,具体如下: ~Map集合是键值对形式存储值的,所以遍历Map集合无非就是获取键和值,根据实际需求,进行获取键和值 1. 无非就是通过map.keySet()获取到值,然后根据键获取到值 for(String s:map.keySet()){ System.out.println("key : "+s+" value : "+map.get(s)); } 2. 通过Map.Entry(

  • Java中关于线程安全的三种解决方式

    三个窗口卖票的例子解决线程安全问题 问题:买票过程中,出现了重票.错票-->出现了线程的安全问题 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,知道线程a操作完ticket时,其他线程才可以开始操作ticket,这种情况即使线程a出现了阻塞,也不能被改变 在Java中,我们通过同步机制,来解决线程的安全问题.(线程安全问题的前提:有共享数据) 方式一:同步代码块 synchroniz

  • 详解Java实现JSONArray转Map的三种实现方式

    目录 第一种 第二种 第三种 本文只是自己常用的三种,自己总结一下,不是只有这三种,杠精走开: JSONArray数据 [ { "flagType": 1, "flagIcon": "1.jpg" }, { "flagType": 2, "flagIcon": "2.jpg" }, { "flagType": 3, "flagIcon": &quo

  • JavaScript数组深拷贝和浅拷贝的两种方法

    例如这个例子: 复制代码 代码如下: var arr = ["One","Two","Three"]; var arrto = arr;arrto[1] = "test";document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,test,Threedocument.writeln("数组的新值

  • Java 字符终端上获取输入三种的方式分享

    在Java 字符终端上获取输入有三种方式: 1.java.lang.System.in (目前JDK版本均支持)2.java.util.Scanner (JDK版本>=1.5)3.java.io.Console(JDK版本>=1.6),特色:能不回显密码字符 参考:这里记录Java中从控制台读入信息的几种方式(1)JDK 1.4(JDK 1.5和JDK 1.6也都兼容这种方法) 复制代码 代码如下: public class TestConsole1 {      public static

  • 浅谈Java虚拟机对内部锁的四种优化方式

    自Java 6/Java 7开始,Java虚拟机对内部锁的实现进行了一些优化.这些优化主要包括锁消除(Lock Elision).锁粗化(Lock Coarsening).偏向锁(Biased Locking)以及适应性锁(Adaptive Locking).这些优化仅在Java虚拟机server模式下起作用(即运行Java程序时我们可能需要在命令行中指定Java虚拟机参数"-server"以开启这些优化). 1 锁消除 锁消除(Lock Elision)是JIT编译器对内部锁的具体实

随机推荐