实例分析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);
  }
}
(0)

相关推荐

  • 实例分析java对象的序列化和反序列化

    引言: 序列化是将对象的状态信息转换为可以存储或传输的形式的过程,在序列化期间,对象将其带你过去的状态写入到临时或持储存区,反序列化就是重新创建对象的过程,此对象来自于临时或持久储存区. 序列化的作用: 就好比如存储数据到数据库,将一些数据持久化到数据库中,而有时候需要将对象持久化,虽然说将对象状态持久化的方式有很多,但是java给我们提供了一种很便捷的方式,那就是序列化,序列化可以实现对象到文件之间的直接转换,实现细节对我们隐藏. 具体的三种用途: •将对象的状态信息持久化保存到硬盘上 •将对

  • java对象转型实例分析

    本文实例讲述了java对象转型的概念,分享给大家供大家参考.具体方法如下: 对象转型(casting)注意事项如下: 1.一个基类的引用类型变量可以"指向"其子类的对象. 2.一个基类的引用不可以访问其子类对象新增加的成员(属性和方法). 3.可以使用 引用变量 instanceof 类名 来判断该引用型变量所"指向"的对象是否属于该类或该类的子类. 4.子类的对象可以当做基类的对象来使用称作向上转型(upcasting),反之成为向下转型(downcasting)

  • java线程池对象ThreadPoolExecutor的深入讲解

    使用线程池的好处 1.降低资源消耗 可以重复利用已创建的线程降低线程创建和销毁造成的消耗. 2.提高响应速度 当任务到达时,任务可以不需要等到线程创建就能立即执行. 3.提高线程的可管理性 线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配.调优和监控 ThreadPoolExecutor 介绍: java 提供的线程池类: ThreadPoolExecutor 作用: 两个作用: 1,用于分离执行任务和当前线程: 2,主要设计初衷:重复利用T

  • 详细讲述Java中的对象转型

    向上转型:子类对象转为父类,父类可以是接口.公式:Father f = new Son();Father是父类或接口,son是子类. 向下转型:父类对象转为子类.公式:Son s = (Son)f; 我们将形参设为父类Animal类型,当执行test.f(c)时,内存情况如下图: c作为Cat类型传入,Animal a作为形参,相当于执行了Animal a = new Cat(),这时a和c同时指向Cat对象,但此时a不能访问Cat类扩展的数据成员,所以再将a强转成Cat类型即可.如果不存在这样

  • java中对象的序列化与反序列化深入讲解

    引言: 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化.可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间.序列化是为了解决在对对象流进行读写操作时所引发的问题. 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对 象,当有 10万用户并发访问,就有可能出现10万个Session对

  • Java对象序列化操作详解

    本文实例讲述了Java对象序列化操作.分享给大家供大家参考,具体如下: 当两个进程在进行远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都会以二进制序列的形式在网络上传送.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收方则需要把字节序列再恢复为Java对象. 只能将支持 java.io.Serializable 接口的对象写入流中.每个 serializable 对象的类都被编码,编码内容包括类名和类签名.对象的字段值和数组值,以及从初始对象中引用的其他所有对象

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

    引言: 在Object基类中,有一个方法叫clone,产生一个前期对象的克隆,克隆对象是原对象的拷贝,由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用.首先看一下容易犯的几个小问题 clone方法是Object类的,并不是Cloneable接口的,Cloneable只是一个标记接口,标记接口是用用户标记实现该接口的类具有某种该接口标记的功能,常见的标记接口有三个:Serializable.Cloneable

  • JAVA对象中使用 static 和 String 基础探究

    目录 前言 原题 static 简介 使用 String == 与 equals() 常量与非常量 intern() JAVA 源码 native 源码 使用 总结 前言 跟同学在讨论 JAVA 期末试题时,对于一些 static 和 String 在对象中的使用方法,若有所思,特此记录一下,也祝没有对象的友友可以自己 new 一个出来! 那我们先来看一看试卷里的原题: 原题 主要就是两个类 MyClass.java 和 TestMyClass.java,填代码的部分就直接跳过了,然后就是输出结

  • 通过实例分析java多态

    这篇文章主要介绍了通过实例分析java多态,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 首先来看这样的一段代码,其中对于类的定义如下: class Parent{ public int myValue=100; public void printValue() { System.out.println("Parent.printValue(),myValue="+myValue); } } class Child extends P

  • 从汇编码分析java对象的创建过程(推荐)

    源码: class T { int m = 8; } T t = new T(); 汇编码: 0 new #2 <T> 3 dup 4 invokespecial #3 <T.<init>> 7 astore_1 8 return new #2 申请内存,在堆里面创建一个新对象. 半初始化,新建对象中的m值是0. dup 复制操作,因为invokespecial会消耗一份引用,所以先复制一份 invokespecial 4 invokespecial #3 <T.

  • 分析java并发中的wait notify notifyAll

    一.前言 java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题其实不用多说,大多数人都能回答出最主要的两点区别: sleep 是线程的方法, wait / notify / notifyAll 是 Object 类的方法: sleep 不会释放当前线程持有的锁,到时间后程序会继续执行,wait 会释放线程持有的锁并挂起,直到通过 notify 或者 notifyAll 重新获得锁. 另外还有一些参数.异常等区别,不细说了.本文重点记录一下 wait / notify

  • 实例分析Java单线程与多线程

    线程:每一个任务称为一个线程,线程不能独立的存在,它必须是进程的一部分 单线程:般常见的Java应用程序都是单线程的,比如运行helloworld的程序时,会启动jvm进程,然后运行main方法产生线程,main方法也被称为主线程. 多线程:同时运行一个以上线程的程序称为多线程程序,多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的. 单线程代码例子: public class SingleThread { public static void main(String[] args

  • 实例分析Java Class的文件结构

    学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号,说"一次编写,到处运行",其实说到无关性,Java平台还有另外一个无关 性那就是语言无关性,要实现语言无关性,那么Java体系中的class的文件结构或者说是字节码就显得相当重要了,其实Java从刚开始的时候就有两套 规范,一个是Java语言规范,另外一个是Java虚拟机规范,Java语言规范只是规定了Java语言相关的约束以及规则,而虚拟机规范则才是真正从跨 平台的角度去设计的.今天我们就以一个实际的例子来看看

  • PHP设计模式之迭代器模式Iterator实例分析【对象行为型】

    本文实例讲述了PHP设计模式之迭代器模式Iterator.分享给大家供大家参考,具体如下: 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的有用方法,但通常你会处理一组对象或者集合. 集合不一定是均一的.图形用户界面框架中的 Window 对象可以收集任意数量的控制对象 - Menu.Slider 和 Button.并且,集合的实现可以有多种方式:PHP 数字是一个集合,但也是一个散列表,一个链接列表,一个堆栈以及

  • 实例分析java中重载与重写的区别

    本文以实例详细分析了Java中重载与重写的区别,感兴趣的朋友可以参考一下. 一.重载(Overloading): (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载Overloading是一个类中多态性的一种表现. (2)Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义. 调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性. (3) 重载的时候,方

随机推荐