Java 深拷贝与浅拷贝的分析

所属分类: 软件编程 / java 阅读数: 60
收藏 0 赞 0 分享

在正式的进入主题之前,我们先来了解下深拷贝和前拷贝的概念:

浅拷贝:

会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的是基本类型的值;如果属性是内存地址,拷贝的就是内存地址,因此如果一个对象改变了这个地址就会影响到另一个对象;

深拷贝:

不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值;

了解完概念之后,我们来测试下普通的对象赋值操作属于深拷贝还是浅拷贝:

测试代码:

public class DepthCopy { 
  public static void main(String[] args) { 
    Copy first = new Copy("hzw", 24); 
    Copy second = first; 
    second.name = "shanxi"; 
    System.out.println(first.name);//输出shanxi 
  } 
} 
class Copy 
{ 
  public String name; 
  public int age; 
  public Copy(String name,int age) { 
    this.name = name; 
    this.age = age; 
  } 
} 

可以发现,在second将name属性值修改为shanxi之后,first的name属性值也变成了shanxi,这点就可以看出普通的对象赋值属于浅拷贝;

明白了对象之间赋值是浅拷贝之后,接下来我们来看看克隆到底是深拷贝还是浅拷贝,测试代码是让上面的Copy对象实现Cloneable接口里面的clone方法:

public class DepthCopy { 
  public static void main(String[] args) { 
    Copy first = new Copy("hzw", 24); 
    Copy second = null; 
    try { 
      second = (Copy) first.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    second.name = "shanxi"; 
    System.out.println(first.name);//输出: hzw 
    System.out.println(first);//输出: com.hzw.day33.Copy@7f39ebdb 
    System.out.println(second);//输出: com.hzw.day33.Copy@33abb81e 
  } 
} 
class Copy implements Cloneable 
{ 
  public String name; 
  public int age; 
  public Copy(String name,int age) { 
    this.name = name; 
    this.age = age; 
  } 
  @Override 
  protected Object clone() throws CloneNotSupportedException { 
    return super.clone(); 
  } 
} 

可以看出原先创建出的对象first和克隆创建出的对象second是两个实例,因此对于second中name属性的修改并不会影响first中的name属性;但是,我们并不能单纯的认为克隆就是深拷贝的,比如下面这个例子:

public class DepthCopy { 
  public static void main(String[] args) { 
    Student student = new Student(95); 
    Copy first = new Copy("hzw", 24,student); 
    Copy second = null; 
    try { 
      second = (Copy) first.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    second.name = "shanxi"; 
    second.student.score = 60; 
    System.out.println(first == second);//false 
    System.out.println(first.student == second.student);//true 
    System.out.println(first.student.score);//60 
  } 
} 
class Copy implements Cloneable 
{ 
  public String name; 
  public int age; 
  public Student student; 
  public Copy(String name,int age,Student student) { 
    this.name = name; 
    this.age = age; 
    this.student = student; 
  } 
  @Override 
  protected Object clone() throws CloneNotSupportedException { 
    return super.clone(); 
  } 
} 
class Student  
{ 
  public int score; 
  public Student(int score) { 
    this.score = score; 
  } 
} 

看到没有呢?我们通过克隆的方式创建了second,很明显发现first和second是两个实例,因为first == second输出为false,但是first和second里面的student对象却是一样的,通过second修改了student的score值之后,first里面student的score也发生了改变,这也就是说first和second里面的student是相同的,这也就说明了克隆是浅拷贝的,我们要想实现克隆的深拷贝,必须让Copy对象里面的Student对象也要实现Cloneable接口里面的clone方法,并且在Copy里面的克隆方法返回Student的一个克隆即可,这样就可以保证Student的唯一啦,修改之后的代码如下:

public class DepthCopy { 
  public static void main(String[] args) { 
    Student student = new Student(95); 
    Copy first = new Copy("hzw", 24,student); 
    Copy second = null; 
    try { 
      second = (Copy) first.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    second.name = "shanxi"; 
    second.student.score = 60; 
    System.out.println(first == second);//false 
    System.out.println(first.student == second.student);//false 
    System.out.println(first.student.score);//95 
    System.out.println(second.student.score);//60 
  } 
} 
class Copy implements Cloneable 
{ 
  public String name; 
  public int age; 
  public Student student; 
  public Copy(String name,int age,Student student) { 
    this.name = name; 
    this.age = age; 
    this.student = student; 
  } 
  @Override 
  protected Object clone() throws CloneNotSupportedException { 
    Copy copy = (Copy)super.clone(); 
    copy.student = (Student) student.clone(); 
    return copy; 
  } 
} 
class Student implements Cloneable 
{ 
  public int score; 
  public Student(int score) { 
    this.score = score; 
  } 
  @Override 
  protected Object clone() throws CloneNotSupportedException { 
    return super.clone(); 
  } 
} 

可以看到此时first和second和first.student和second.student都不是相同的,因此我们修改second的student的score之后并没有影响到first里的student的score值,达到了深拷贝的目的;

但是,仔细一想问题就出来了,假如我们上面例子的Student类中也存在引用类型的属性,比如College类,那么我们必须让College类实现Cloneable接口,然后在Student类里面的clone方法里面调用College类的clone方法,在Copy类的clone方法中调用Student类的clone方法,发现没有了,这个过程好复杂,必须让类中的有关引用类型全部实现Cloneable接口,感觉好麻烦是不是,好的,接下来就该牛人登场了;

解决深拷贝问题最好的方式就是采用序列化方式,这样各种类均不用实现Cloneable接口的,直接序列化反序列化就可以啦,我们来见识下吧。

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.io.Serializable; 
 
public class DepthCopy { 
  public static void main(String[] args) { 
    College school = new College("nongda"); 
    Student student = new Student(95, school); 
    Copy copy = new Copy("hzw",23, student); 
    Copy another = null;//表示反序列化出来的类实例 
    //进行序列化操作 
    try { 
      FileOutputStream fos = new FileOutputStream(new File("d:/copy.txt")); 
      ObjectOutputStream oos = new ObjectOutputStream(fos); 
      oos.writeObject(copy); 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
    //进行反序列化操作 
    FileInputStream fis; 
    try { 
      fis = new FileInputStream(new File("d:/copy.txt")); 
      ObjectInputStream ois = new ObjectInputStream(fis); 
      another = (Copy) ois.readObject(); 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
    System.out.println(copy == another);//false 
    System.out.println(copy.student == another.student);//false 
    System.out.println(copy.student.school == another.student.school);//false 
    another.student.school.schoolName = "wuda"; 
    System.out.println(copy.student.school.schoolName);//nongda 
  } 
} 
class Copy implements Serializable 
{ 
  public String name; 
  public int age; 
  public Student student; 
  public Copy(String name,int age,Student student) { 
    this.name = name; 
    this.age = age; 
    this.student = student; 
  } 
} 
class Student implements Serializable 
{ 
  public int score; 
  public College school; 
  public Student(int score,College school) { 
    this.score = score; 
    this.school = school; 
  } 
} 
class College implements Serializable 
{ 
  public String schoolName; 
  public College(String schoolName) { 
    this.schoolName = schoolName; 
  } 
} 

从输出就可以看出来,反序列化之后生成的对象完全就是对原对象的一份拷贝,除了属性值相同之外并不和原对象有任何关系,因此当我们修改反序列化生成对象的schoolName为"wuda"的时候并没有修改原来实例的schoolName值,还是输出"nongda",因此达到了真正的深拷贝效果,但是要想实现序列化,所有的有关类都必须实现Serializable接口,这总也比既实现Cloneable接口又实现clone方法更方便吧。

以上就是对Java 深拷贝和浅拷贝的详细讲解,有需要的可以参考下。

更多精彩内容其他人还在看

Java实现一个简单的文件上传案例示例代码

这篇文章主要介绍了Java实现一个简单的文件上传案例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

详解在spring中使用JdbcTemplate操作数据库的几种方式

这篇文章主要介绍了详解在spring中使用JdbcTemplate操作数据库的几种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

java中的switch case语句使用详解

这篇文章主要介绍了java中的switch case语句使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

深入理解Java 线程池

这篇文章主要介绍了Java 线程池的相关资料,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
收藏 0 赞 0 分享

详解mybatis #{}和${}的区别、传参、基本语法

这篇文章主要介绍了mybatis #{}和${}的区别、传参、基本语法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

MyBatis 中 ${}和 #{}的正确使用方法(千万不要乱用)

这篇文章主要介绍了MyBatis 中 ${}和 #{}的正确使用方法,本文给大家提到了MyBatis 中 ${}和 #{}的区别,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Mybatis中的 ${} 和 #{}区别与用法

这篇文章主要介绍了Mybatis中的 ${} 和 #{}区别与用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Maven安装过程图文详解

这篇文章主要介绍了Maven安装过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
收藏 0 赞 0 分享

SpringBoot登录拦截配置详解(实测可用)

这篇文章主要介绍了SpringBoot登录拦截配置详解(实测可用),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

JAVA 内存溢出案例汇总

这篇文章主要介绍了JAVA 内存溢出案例的汇总,文中讲解非常细致,帮助各位工作学习时避免内存溢出,感兴趣的朋友可以了解下
收藏 0 赞 0 分享
查看更多