深入了解Java对象的克隆

  今天要介绍一个概念,对象的克隆。本篇有一定难度,请先做好心理准备。看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充。

  克隆,自然就是将对象重新复制一份,那为什么要用克隆呢?什么时候需要使用呢?先来看一个小栗子:

  简单起见,我们这里用的是Goods类的简单版本。

public class Goods {
  private String title;
  private double price;

  public Goods(String aTitle, double aPrice){
    title = aTitle;
    price = aPrice;
  }

  public void setPrice(double price) {
    this.price = price;
  }

  public void setTitle(String title) {
    this.title = title;
  }
  //用于打印输出商品信息
  public void print(){
    System.out.println("Title:"+title+" Price:"+price);
  }
}

  然后我们来使用这个类。

public class GoodsTest {
  public static void main(String[] args){
    Goods goodsA = new Goods("GoodsA",20);
    Goods goodsB = goodsA;
    System.out.println("Before Change:");
    goodsA.print();
    goodsB.print();

    goodsB.setTitle("GoodsB");
    goodsB.setPrice(50);
    System.out.println("After Change:");
    goodsA.print();
    goodsB.print();
  }
}

  我们创建了一个Goods对象赋值给变量goodsA,然后又创建了一个Goods变量,并把goodsA赋值给它,先调用Goods的print方法输出这两个变量中的信息,然后调用Goods类中的setTitle和setPrice方法来修改goodsB中的对象内容,再输出两个变量中的信息,下面是输出:

Before Change:
Title:GoodsA Price:20.0
Title:GoodsA Price:20.0
After Change:
Title:GoodsB Price:50.0
Title:GoodsB Price:50.0

  这里我们发现了灵异事,我们明明修改的是goodsB的内容,可是goodsA的内容也同样发生了改变,这究竟是为什么呢?别心急,且听我慢慢道来。

  在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。使用等号赋值都是进行值传递的,如将一个整数型变量赋值给另一个整数型变量,那么后者将存储前者的值,也就是变量中的整数值,对于基本类型如int,double,char等是没有问题的,但是对于对象,则又是另一回事了,这里的goodsA和goodsB都是Goods类对象的变量,但是它们并没有存储Goods类对象的内容,而是存储了它的地址,也就相当于C++中的指针,如果对于指针不了解,那我就再举个栗子好了。我们之前举过一个栗子,把计算机比作是仓库管理员,内存比作是仓库,你要使用什么类型的变量,就需要先登记,然后管理员才会把东西给你,但如果是给你分配一座房子呢?这时候不是把房子搬起来放到登记簿粒,而是登记下房子的地址,这里的地址就是我们的类对象变量里记录的内容,所以,当我们把一个类对象变量赋值给另一个类对象变量,如goodsB = goodsA时,实际上只是把A指向的对象地址赋值给了B,这样B也同样指向这个地址,所以这时候,goodsA和goodsB操作的是同一个对象。

  所以,如果只是简单的赋值的话,之后对于goodsA和goodsB的操作都将影响同一个对象,这显然不是我们的本意。也许你还会问,直接再new一个对象不就好了,确实如此,但有时候,如果我们需要保存一个goodsA的副本,那就不仅仅要new一个对象,还需要进行一系列赋值操作才能将我们的新对象设置成跟goodsA对象一样,而且Goods类越复杂,这个操作将会越繁琐,另外使用clone方法还进行本地优化,效率上也会快很多,总而言之,就是简单粗暴。

  那如何使用克隆呢?这里我们就要介绍我们牛逼哄哄的Object类了,所有的类都是Object类的子类,虽然我们并没有显式声明继承关系,但所有类都难逃它的魔掌,它有两个protected方法,其中一个就是clone方法。

  下面我来展示一波正确的骚操作:

//要使用克隆方法需要实现Cloneable接口
public class Goods implements Cloneable{
  private String title;
  private double price;

  public Goods(String aTitle, double aPrice){
    title = aTitle;
    price = aPrice;
  }

  public void setPrice(double price) {
    this.price = price;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public void print(){
    System.out.println("Title:"+title+" Price:"+price);
  }

  //这里重载了接口的clone方法
  @Override
  protected Object clone(){
    Goods g = null;    //这里是异常处理的语句块,可以先不用了解,只要知道是这样使用就好,之后的文章中会有详细的介绍
    try{
      g = (Goods)super.clone();
    }catch (CloneNotSupportedException e){
      System.out.println(e.toString());
    }
    return g;
  }
}

  其实修改的地方只有两个,一个是定义类的时候实现了Cloneable接口,关于接口的知识在之后会有详细说明,这里只要简单理解为是一种规范就行了,然后我们重载了clone方法,并在里面调用了父类也就是(Object)的clone方法。可以看到我们并没有new一个新的对象,而是使用父类的clone方法进行克隆,关于try catch的知识这里不做过多介绍,之后会有文章做详细说明,这里只需要理解为try语句块里是一个可能发生错误的代码,catch会捕获这种错误并进行处理。

  接下来我们再使用这个类的克隆方法:

public class GoodsTest {
  public static void main(String[] args){
    Goods goodsA = new Goods("GoodsA",20);
    Goods goodsB = (Goods)goodsA.clone();
    System.out.println("Before Change:");
    goodsA.print();
    goodsB.print();

    goodsB.setTitle("GoodsB");
    goodsB.setPrice(50);
    System.out.println("After Change:");
    goodsA.print();
    goodsB.print();
  }
}

  我们仅仅是把赋值改成了调用goodsA的clone方法并进行类型转换。输出如下:

Before Change:
Title:GoodsA Price:20.0
Title:GoodsA Price:20.0
After Change:
Title:GoodsA Price:20.0
Title:GoodsB Price:50.0

  看,这样不就达到我们目的了吗?是不是很简单?

  但是别高兴的太早,关于克隆,还有一点内容需要介绍。

  克隆分为浅克隆和深克隆。我们上面使用的只是浅克隆,那两者有什么区别呢?这里再举一个栗子,使用的是简化版的Cart类:

public class Cart implements Cloneable{
  //实例域
  Goods goodsList = new Goods("",0);//简单起见,这里只放了一个商品
  double budget = 0.0;//预算

  //构造函数
  public Cart(double aBudget){
    budget = aBudget;
  }

  //获取预算
  public double getBudget() {
    return budget;
  }

  //修改预算
  public void setBudget(double aBudget) {
    budget = aBudget;
  }

  //这里只是简单的将商品进行了赋值
  public void addGoods(Goods goods){
    goodsList = (Goods) goods.clone();
  }

  //这是为了演示加上的代码,仅仅将商品标题修改成新标题
  public void changeGoodsTitle(String title){
    goodsList.setTitle(title);
  }

  //打印商品信息
  public void print(){
    System.out.print("Cart内的预算信息:"+budget+" 商品信息:");
    goodsList.print();
  }

  //重载clone方法
  @Override
  protected Object clone(){
    Cart c = null;
    try{
      c = (Cart)super.clone();
    }catch (CloneNotSupportedException e ){
      e.printStackTrace();
    }
    return c;
  }
}

  这里将goodsList由数组改成了单个对象变量,仅仅用于演示方便,还增加了一个changeGoodsTitle方法,用于将商品的标题修改成另一个标题,接下来修改一下GoodsTest类:

public class GoodsTest {
  public static void main(String[] args){
    Goods goodsA = new Goods("GoodsA",20);//新建一个商品对象
    Cart cartA = new Cart(5000);//新建一个购物车对象
    cartA.addGoods(goodsA);//添加商品
    Cart cartB = (Cart) cartA.clone();//使用浅克隆
     //输出修改前信息
    System.out.println("Before Change:");
    cartA.print();
    cartB.print();
     //修改购物车A中的商品标题
    cartA.changeGoodsTitle("NewTitle");     //重新输出修改后的信息
    System.out.println("After Change:");
    cartA.print();
    cartB.print();
  }
}

  输出信息:

Before Change:
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
After Change:
Cart内的预算信息:5000.0 商品信息:Title:NewTitle Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:NewTitle Price:20.0

  我们发现,虽然我们调用的是cartA中的方法修改购物车A中的商品信息,但购物车B中的信息同样被修改了,这是因为使用浅克隆模式的时候,成员变量如果是对象等复杂类型时,仅仅使用的是值拷贝,就跟我们之前介绍的那样,所以cartB虽然是cartA的一个拷贝,但是它们的成员变量goodsList却共用一个对象,这样就藕断丝连了,显然不是我们想要的效果,这时候就需要使用深拷贝了,只需要将Cart类的clone方法修改一下即可:

  @Override
  protected Object clone(){
    Cart c = null;
    try{
      c = (Cart)super.clone();
      c.goodsList = (Goods) goodsList.clone();//仅仅添加了这段代码,将商品对象也进行了克隆
    }catch (CloneNotSupportedException e ){
      e.printStackTrace();
    }
    return c;
  }

 现在再来运行一下:

Before Change:
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
After Change:
Cart内的预算信息:5000.0 商品信息:Title:NewTitle Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0

  这样就得到了我们想要的结果了。

  这样,对象的拷贝就讲完了。

  吗?

  哈哈哈哈,不要崩溃,并没有,还有一种更复杂的情况,那就是当你的成员变量里也包含引用类型的时候,比如Cart类中有一个CartB类的成员变量,CartB类中同样存在引用类型的成员变量,这时候,就存在多层克隆的问题了。这里再介绍一个骚操作,只需要了解即可,那就是序列化对象。操作如下:

import java.io.*;

public class Cart implements Serializable{
  //实例域
  Goods goodsList = new Goods("",0);//简单起见,这里只放了一个商品
  double budget = 0.0;//预算

  //构造函数
  public Cart(double aBudget){
    budget = aBudget;
  }

  //获取预算
  public double getBudget() {
    return budget;
  }

  //修改预算
  public void setBudget(double aBudget) {
    budget = aBudget;
  }

  //这里只是简单的将商品进行了赋值
  public void addGoods(Goods goods){
    goodsList = (Goods) goods.clone();
  }

  //这是为了演示加上的代码,仅仅将商品标题修改成新标题
  public void changeGoodsTitle(String title){
    goodsList.setTitle(title);
  }

  //打印商品信息
  public void print(){
    System.out.print("Cart内的预算信息:"+budget+" 商品信息:");
    goodsList.print();
  }
  //这里是主要是骚操作
  public Object deepClone() throws IOException, OptionalDataException,ClassNotFoundException {
    // 将对象写到流里
    ByteArrayOutputStream bo = new ByteArrayOutputStream();
    ObjectOutputStream oo = new ObjectOutputStream(bo);
    oo.writeObject(this);
    // 从流里读出来
    ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
    ObjectInputStream oi = new ObjectInputStream(bi);
    return (oi.readObject());
  }
}

  关于这种方法我就不多做介绍了,大家只需要知道有这样一种方法就行了,以后如果遇到了需要使用这种情况,就知道该怎样处理了。

  这里总结一下,对象的克隆就是把一个对象的当前状态重新拷贝一份到另一个新对象中,两个对象变量指向不同的对象,浅克隆仅仅调用super.clone()方法,对成员变量也只是简单的值拷贝,所以当成员变量中有数组,对象等复杂类型的时候,就会存在藕断丝连的混乱关系,深拷贝不仅仅调用super.clone()方法进行对象拷贝,将对象中的复杂类型同样进行了拷贝,这样两个对象就再无瓜葛,井水不犯河水了。

  至此,对象的克隆就真正的结束了,欢迎大家继续关注!如有不懂的问题可以留言。也欢迎各位大佬来批评指正。喜欢我的教程的话记得动动小手点下推荐,也欢迎关注我的博客。

以上就是深入了解Java对象的克隆的详细内容,更多关于Java 克隆的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java编程实现对象克隆(复制)代码详解

    克隆,想必大家都有耳闻,世界上第一只克隆羊多莉就是利用细胞核移植技术将哺乳动物的成年体细胞培育出新个体,甚为神奇.其实在Java中也存在克隆的概念,即实现对象的复制. 本文将尝试介绍一些关于Java中的克隆和一些深入的问题,希望可以帮助大家更好地了解克隆. 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适

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

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

  • Java中对象的深复制(深克隆)和浅复制(浅克隆)介绍

    1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵深复制(深克隆) 被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量.那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象.换言之,深复制把要复制的对象所引用的对象都复制了一遍. 2.Java的clone()方法 ⑴clone方法将对象复制了一份并返回

  • java 对象的克隆(浅克隆和深克隆)

    java 对象的克隆 一.对象的浅克隆 (1)需要克隆类需要重写Object类的clone方法,并且实现Cloneable接口(标识接口,无需实现任何方法) (2)当需要克隆的对象中维护着另外一个引用对象,浅克隆不会克隆另外一个引用对下,而是直接复制维护的另外一个引用对象的地址. (3)对象的浅克隆也不会调用到构造方法. 以下为对象的浅克隆的一个例子: package com.clone; import java.io.Serializable; /** * Description: * 实现了

  • Java中对象的序列化方式克隆详解

    Java 序列化技术可以使你将一个对象的状态写入一个Byte 流里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象. 简述: 用字节流的方式,复制Java对象 代码: 流克隆复制函数 public static Object deepClone(Object obj){ if(obj == null){ return null; } try { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); Ob

  • 深入JAVA对象深度克隆的详解

    有时候,我们需要把对象A的所有值复制给对象B(B = A),但是这样用等号给赋值你会发现,当B中的某个对象值改变时,同时也会修改到A中相应对象的值!也许你会说,用clone()不就行了?!你的想法只对了一半,因为用clone()时,除了基础数据和String类型的不受影响外,其他复杂类型(如集合.对象等)还是会受到影响的!除非你对每个对象里的复杂类型又进行了clone(),但是如果一个对象的层次非常深,那么clone()起来非常复杂,还有可能出现遗漏!既然用等号和clone()复制对象都会对原来

  • 基于序列化存取实现java对象深度克隆的方法详解

    我们知道,在java中,将一个非原型类型类型的对象引用,赋值给另一个对象的引用之后,这两个引用就指向了同一个对象,如: 复制代码 代码如下: public class DeepCloneTest { private class CloneTest {  private Long myLong = new Long(1); } public static void main(String args[]) {  new DeepCloneTest().Test(); } public void Te

  • 深入了解Java对象的克隆

    今天要介绍一个概念,对象的克隆.本篇有一定难度,请先做好心理准备.看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充. 克隆,自然就是将对象重新复制一份,那为什么要用克隆呢?什么时候需要使用呢?先来看一个小栗子: 简单起见,我们这里用的是Goods类的简单版本. public class Goods { private String title; private double price; public Goods(String aTitle, double aPri

  • JAVA 对象创建与对象克隆

    目录 一.对象的4种创建方式 二.通过new创建对象 三.反射 四.克隆对象 浅拷贝 深拷贝 五.反序列化 六.补充 一.对象的4种创建方式 new 创建 反射 克隆 反序列化 二.通过new创建对象 一般情况下,对象通过new 关键字创建,首先会在堆上给对象分配空间,然后执行构造函数进行一系列的初始化,在分配的内存空间上为一众属性赋值:完成初始化后再将堆区对象的引用传递给栈区,最终参与程序的运行. 三.反射 调用Java.lang.Class或者java.lang.reflect.Constr

  • 深入理解Java中的克隆

    前言 Java克隆(Clone)是Java语言的特性之一,但在实际中应用比较少见.但有时候用克隆会更方便更有效率. 对于克隆(Clone),Java有一些限制: 1.被克隆的类必须自己实现Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制.Cloneable 接口实际上是个标识接口,没有任何接口方法. 2.实现Cloneable接口的类应该使用公共方法重写 Object.clone(它是受保护的).某个对象实现了此接口就克隆它是不可能的.即使

  • java对象拷贝详解及实例

    java对象拷贝详解及实例 Java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的: @Test public void testassign(){ Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); Person p2=p1; System.out.println(p1==p2);//true } 如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,

  • 浅谈Java中的克隆close()和赋值引用的区别

    学生类Student: package 克隆clone; /*要克隆必须实现这个借口:Cloneable,以标记这个对象可以克隆 Cloneable:此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制. 这个接口是标记接口,告诉我们实现该接口的类就可以实现对象的复制了. */ public class Student implements Cloneable { private String name; private int ag

  • Java对象的复制三种方式(小结)

    1.概述 在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的.例如下面程序展示的情况: class Student { private int number; public int getNumber() { return number; } public void setNumber(int number)

随机推荐