实例分析java对象中浅克隆和深克隆

 更新时间:2018年10月10日 15:46:08   投稿:laozhang  
在本篇文章中我们给大家分享了关于java对象中浅克隆和深克隆的相关知识点和相关代码内容,有兴趣的朋友们学习下。

引言:

在Object基类中,有一个方法叫clone,产生一个前期对象的克隆,克隆对象是原对象的拷贝,由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用。首先看一下容易犯的几个小问题

clone方法是Object类的,并不是Cloneable接口的,Cloneable只是一个标记接口,标记接口是用用户标记实现该接口的类具有某种该接口标记的功能,常见的标记接口有三个:Serializable、Cloneable、RandomAccess,没有实现Cloneable接口,那么调用clone方法就会爆出CloneNotSupportedException异常。

Object类中的clone方法是protected修饰的,这就表明我们在子类中不重写此方法,就在子类外无法访问,因为这个protected权限是仅仅能在Object所在的包和子类能访问的,这也验证了子类重写父类方法权限修饰符可以变大但不能变小的说法。

protected native Object clone() throws CloneNotSupportedException;

重写clone方法,内部仅仅是调用了父类的clone方法,其实是为了扩大访问权限,当然你可以把protected改为public,以后再继承就不用重写了。当然只是浅克隆的clone函数,深克隆就需要修改了。

@Override  
protected Object clone() throws CloneNotSupportedException {    
 return super.clone();
}

属性是String的情况,String也是一个类,那String引用类型吗?String的表现有的像基本类型,归根到底就是因为String不可改变,克隆之后俩个引用指向同一个String,但当修改其中的一个,改的不是String的值,却是新生成一个字符串,让被修改的引用指向新的字符串。外表看起来就像基本类型一样。

浅克隆:

浅克隆就是引用类型的属性无法完全复制,类User中包含成绩属性Mark,Mark是由Chinese和math等等组成的,浅克隆失败的例子

class Mark{
  private int chinese;
  private int math;
  public Mark(int chinese, int math) {
    this.chinese = chinese;
    this.math = math;
  }
 
  public void setChinese(int chinese) {
    this.chinese = chinese;
  }
 
  public void setMath(int math) {
    this.math = math;
  }
 
  @Override
  public String toString() {
    return "Mark{" +
        "chinese=" + chinese +
        ", math=" + math +
        '}';
  }
}
public class User implements Cloneable{
  private String name;
  private int age;
  private Mark mark;
 
  public User(String name, int age,Mark mark) {
    this.name = name;
    this.age = age;
    this.mark = mark;
  }
 
  @Override
  public String toString() {
    return "User{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", mark=" + mark +
        '}';
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
 
  public static void main(String[] args) throws CloneNotSupportedException {
    Mark mark = new Mark(100,99);
    User user = new User("user",22,mark);
    User userClone = (User) user.clone();
    System.out.println("原user:"+user);
    System.out.println("克隆的user:"+userClone);
    //修改引用类型的mark属性
    user.mark.setMath(60);
    System.out.println("修改后的原user:"+user);
    System.out.println("修改后的克隆user:"+userClone);
  }
}

输出结果为:   

原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}

很清楚的看到user的mark更改后,被克隆的user也修改了。而要想不被影响,就需要深克隆了。

深克隆:

方式一:clone函数的嵌套调用

既然引用类型无法被完全克隆,那将引用类型也实现Cloneable接口重写clone方法,在User类中的clone方法调用属性的克隆方法,也就是方法的嵌套调用

class Mark implements Cloneable{
  private int chinese;
  private int math;
  public Mark(int chinese, int math) {
    this.chinese = chinese;
    this.math = math;
  }
  public void setChinese(int chinese) {
    this.chinese = chinese;
  }
  public void setMath(int math) {
    this.math = math;
  }
  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
  @Override
  public String toString() {
    return "Mark{" +
        "chinese=" + chinese +
        ", math=" + math +
        '}';
  }
}
public class User implements Cloneable{
  private String name;
  private int age;
  private Mark mark;
 
  public User(String name, int age,Mark mark) {
    this.name = name;
    this.age = age;
    this.mark = mark;
  }
 
  @Override
  public String toString() {
    return "User{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", mark=" + mark +
        '}';
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException {
    User user = (User) super.clone();
    user.mark = (Mark) this.mark.clone();
    return user;
  }
 
  public static void main(String[] args) throws CloneNotSupportedException {
    Mark mark = new Mark(100,99);
    User user = new User("user",22,mark);
    User userClone = (User) user.clone();
    System.out.println("原user:"+user);
    System.out.println("克隆的user:"+userClone);
    //修改引用类型的mark属性
    user.mark.setMath(60);
    System.out.println("修改后的原user:"+user);
    System.out.println("修改后的克隆user:"+userClone);
  }
}

输出结果为: 

原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}

方式二:序列化

上一种方法已经足够满足我们的需要,但是如果类之间的关系很多,或者是有的属性是数组呢,数组可无法实现Cloneable接口(我们可以在clone方法中手动复制数组),但是每次都得手写clone方法,很麻烦,而序列化方式只需要给每个类都实现一个Serializable接口,也是标记接口,最后同序列化和反序列化操作达到克隆的目的(包括数组的复制)。序列化和反序列化的知识请参照下一篇

import java.io.*;
class Mark implements Serializable {
  private int chinese;
  private int math;
  public Mark(int chinese, int math) {
    this.chinese = chinese;
    this.math = math;
}
  public void setChinese(int chinese) {
    this.chinese = chinese;
  }
  public void setMath(int math) {
    this.math = math;
  }
  @Override
  public String toString() {
    return "Mark{" +
        "chinese=" + chinese +
        ", math=" + math +
        '}';
  }
}
public class User implements Serializable{
  private String name;
  private int age;
  private Mark mark;
 
  public User(String name, int age,Mark mark) {
    this.name = name;
    this.age = age;
    this.mark = mark;
  }
 
  @Override
  public String toString() {
    return "User{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", mark=" + mark +
        '}';
  }
  public static void main(String[] args) throws IOException, ClassNotFoundException {
    Mark mark = new Mark(100,99);
    User user = new User("user",22,mark);
 
    ByteArrayOutputStream bo = new ByteArrayOutputStream();
    ObjectOutputStream oo = new ObjectOutputStream(bo);
    oo.writeObject(user);//序列化
    ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
    ObjectInputStream oi = new ObjectInputStream(bi);
    User userClone = (User) oi.readObject();//反序列化
 
    System.out.println("原user:"+user);
    System.out.println("克隆的user:"+userClone);
    user.mark.setMath(59);
    System.out.println("修改后的原user:"+user);
    System.out.println("修改后的克隆user:"+userClone);
  }
}

输出结果:

原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}

带数组属性的克隆

import java.io.*;
import java.util.Arrays;
 
public class User implements Serializable{
  private String name;
  private int age;
  private int[] arr;
 
  public User(String name, int age, int[] arr) {
    this.name = name;
    this.age = age;
    this.arr = arr;
  }
  @Override
  public String toString() {
    return "User{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", arr=" + Arrays.toString(arr) +
        '}';
  }
  public static void main(String[] args) throws IOException, ClassNotFoundException {
    int[] arr = {1,2,3,4,5,6};
    User user = new User("user",22,arr);
 
    ByteArrayOutputStream bo = new ByteArrayOutputStream();
    ObjectOutputStream oo = new ObjectOutputStream(bo);
    oo.writeObject(user);//序列化
    ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
    ObjectInputStream oi = new ObjectInputStream(bi);
    User userClone = (User) oi.readObject();//反序列化
 
    System.out.println("原user:"+user);
    System.out.println("克隆的user:"+userClone);
    user.arr[1] = 9;
    System.out.println("修改后的原user:"+user);
    System.out.println("修改后的克隆user:"+userClone);
  }
}

相关文章

  • java通过jacob实现office在线预览功能

    java通过jacob实现office在线预览功能

    这篇文章主要为大家详细介绍了java通过jacob实现office在线预览功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • JAVA实现多线程的两种方法实例分享

    JAVA实现多线程的两种方法实例分享

    这篇文章介绍了JAVA实现多线程的两种方法实例分享,有需要的朋友可以参考一下
    2013-08-08
  • 深入理解MyBatis中的一级缓存与二级缓存

    深入理解MyBatis中的一级缓存与二级缓存

    这篇文章主要给大家深入的介绍了关于MyBatis中一级缓存与二级缓存的相关资料,文中详细介绍MyBatis中一级缓存与二级缓存的工作原理及使用,对大家具有一定的参考性学习价值,需要的朋友们下面来一起看看吧。
    2017-06-06
  • java获取Date时间的各种方式汇总

    java获取Date时间的各种方式汇总

    这篇文章针对java获取Date时间的各种方式汇总,有常用的时间获取方式,还有一些其他特殊时间获取方式,感兴趣的小伙伴们可以参考一下
    2015-12-12
  • SpringBoot中的CSRF攻击及预防方法

    SpringBoot中的CSRF攻击及预防方法

    CSRF攻击是一种常见的网络攻击方式,可以通过欺骗用户来执行恶意操作,在Spring Boot应用程序中,我们可以采取多种措施来预防CSRF攻击,本文将给大家介绍一下CSRF攻击以及如何预防攻击,需要的朋友可以参考下
    2023-07-07
  • ScrollView中嵌入ListView只显示一条的解决办法

    ScrollView中嵌入ListView只显示一条的解决办法

    在ScrollView添加一个ListView会导致listview控件显示不全,通常只会显示一条,究竟是什么原因呢?下面脚本之家小编给大家介绍ScrollView中嵌入ListView只显示一条的解决办法,感兴趣的朋友一起学习吧
    2016-05-05
  • Dubbo扩展点SPI实践示例解析

    Dubbo扩展点SPI实践示例解析

    这篇文章主要为大家介绍了Dubbo扩展点SPI实践示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • SpringBoot如何升级到3.0

    SpringBoot如何升级到3.0

    这篇文章主要介绍了SpringBoot如何升级到3.0问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-09-09
  • java实现用户签到BitMap功能实现demo

    java实现用户签到BitMap功能实现demo

    这篇文章主要为大家介绍了java实现用户签到BitMap功能实现demo,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • java8 stream多字段排序的实现

    java8 stream多字段排序的实现

    这篇文章主要介绍了java8 stream多字段排序的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03

最新评论