java克隆之浅拷贝和深拷贝

1、克隆的概述

1.1、什么是克隆?

   ​ 说到克隆,脑中一闪而过的是“克隆羊”多利,再闪而过的是生物中的细胞有丝分裂。一个长得像,一个分裂极快。

   ​ 在学习java设计模式,原型模式的时候,说是提供了一种机制,可以将原始对象复制到新对象,使用的是克隆来复制对象。

   ​ 有了这个概念之后,那么程序语言中为什么需要克隆呢?java是如何实现克隆的呢?

1.2、为什么需要克隆

   ​ 我们通常使用的 =(等号)赋值操作,对基本数据类型而言是值传递,也就是拷贝的它的值,对引用数据类型而言,只是将原对象的引用传递过去,实际上他们都指向的是内存中的同一块地址。

public class TestDemo {

	public static void main(String[] args) {
		A a1 = new A();
		A a2 = a1;
		System.out.println(a1);
		System.out.println(a2);
		System.out.println("a1==a2   "+(a1==a2));
		
		int a = 3;
		int b = a;
		a = 5;
		System.out.println("b="+b);
	}
}
class A{
	
	private String name;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
}

输出结果:

   ​ =号赋值,对引用类型并不是我想要的的克隆效果,因为它并不是为我创建了一个新的对象。当然,我肯定可以再new一遍创建一个新对象啊。那到底有没有一种更简便的方法呢?

   ​ 答案是有的,那就是我们今天的主角克隆。

1.3、如何实现克隆

   ​ 几乎所有语言中,都有克隆的影子,java也不例外,java中也有浅拷贝和深拷贝的概念。

   ​ 在java中,除了基本数据类型(byte、short、int、long、float、double、boolean、char)之外,还有引用数据类型,对应着的操作也是有值传递和引用传递。浅拷贝和深拷贝就是在这个基础上做的区分。

   ​ java在JDK1.0时就已经实现了clone。那么java是如何实现的呢?

   ​ 早期java为了实现对象克隆功能,可谓绞尽脑汁。又不想让所有对象都支持clone,对于一个类是否要支持克隆,还是由用户来选择比较靠谱。那么要有个标记告诉虚拟机才行,但是如何去标记呢?

   ​ 加一个关键字?clone还没有重要到必须要使用一个关键字来修饰class。

   ​ 使用继承?java类又是单继承,如果处于标记是否需要克隆消耗掉基类,也不值得。

   ​ 有人会想到,用注解去标识啊?嗯可以,不过注解是在JDK1.5版本时才出现的。

   ​ 思来想去,就只剩下接口了,接口本身就支持多继承。于是有了Cloneable接口,来做是否可以克隆的标记。

//java中Cloneable接口
 * @author  unascribed
 * @version 1.17, 11/17/05
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable { 
}
//java的Object中的克隆方法,是一个native方法
protected native Object clone() throws CloneNotSupportedException;
//方法描述中写道
/**
 * Creates and returns a copy of this object.
 * x.clone() != x 说明clone产生新对象
 * x.clone().getClass() == x.getClass() 同一个类型
 * x.clone().equals(x)  一般是这样,但并不是绝对的。如果复制的对象包含深层次的可变对象,克隆的是它们的  * 引用                        
 *  如果没有实现Cloneable,将会抛出CloneNotSupportedException
 */

// 一段关于native Object clone()的内部实现
#ifdef ASSERT
  // Just checking that the cloneable flag is set correct
  if (obj->is_array()) {
    guarantee(klass->is_cloneable(), "all arrays are cloneable");
  } else {
    guarantee(obj->is_instance(), "should be instanceOop");
    bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());
    guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
  }
#endif

  // Check if class of obj supports the Cloneable interface.
  // All arrays are considered to be cloneable (See JLS 20.1.5)
  if (!klass->is_cloneable()) {
    ResourceMark rm(THREAD);
    THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
  }

2、浅拷贝

2.1、什么是浅拷贝

   ​ 浅拷贝就是尽可能的少复制。它是对“主”对象的拷贝,它不会拷贝“主”对象深层次的可变对象,只做第一层的拷贝。

2.2、使用clone实现浅拷贝

//新建一个Child类
public class Child {
	private String name;
	private int age;
	
	public Child(String name,int age){
		this.name = name;
		this.age = age;
	}
	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;
	}
}
//新建一个Father类,实现Cloneable接口
public class Father implements Cloneable{
	
	private String name;
	private int age;
	final String Fina = new String("final");
	private Child child;
	
	public Father(String name ,int age) {
		this.name =name;
		this.age = age;
	}
	
	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 Child getChild() {
		return child;
	}
	public void setChild(Child child) {
		this.child = child;
	}
	
	//浅拷贝
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
}

​    写个Test类

public class Test {

	public static void main(String[] args) throws Exception {
		//父亲叫张三
		Father fa = new Father("张三", 30);
		//父亲有个女儿叫张萌
		Child child = new Child("张萌", 8);
		fa.setChild(child);
		
		//父亲张三有个弟弟叫账张四
		Father fa2 = (Father) fa.clone();
		System.out.println("fa2==fa:"+(fa2==fa)); //false fa2是一个新对象
		System.out.println(" ");
		
		System.out.println("改变克隆对象第一层对象");
		fa2.setAge(29);
		fa2.setName("张四");
		System.out.println("fa.getName()="+fa.getName()+"fa.getAge()="+fa.getAge());
		System.out.println(" ");
		
		System.out.println("改变克隆对象的深层次引用类型对象");
		fa2.getChild().setAge(9);
		fa2.getChild().setName("张飞");
		System.out.println("fa.getChild() == fa2.getChild() :"+(fa.getChild() == fa2.getChild()));
		System.out.println("fa.getChild().getAge():"+fa.getChild().getAge()+"\nfa.getChild().getName():"+fa.getChild().getName());
		
	}
}

   输出结果:克隆产生新对象,但是深层次的可变对象拷贝的是对象的引用

3、深拷贝

3.1、什么是深拷贝

   ​ 深拷贝就是复制一切。深拷贝相比于浅拷贝速度慢并且开销大,但是拷贝前后两个对象互不影响。

3.2、使用clone实现深拷贝

//Child也实现Cloneable
public class Child implements Cloneable{
	private String name;
	private int age;
	
	public Child(String name,int age){
		this.name = name;
		this.age = age;
	}
	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;
	}
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
}
//父类在拷贝的时候,同时也将深层次的引用对象也进行拷贝
public class Father implements Cloneable{
	
	private String name;
	private int age;
	final String Fina = new String("final");
	private Child child;
	
	public Father(String name ,int age) {
		this.name =name;
		this.age = age;
	}
	
	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 Child getChild() {
		return child;
	}
	public void setChild(Child child) {
		this.child = child;
	}
	
	//深拷贝
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Father fa = null;
		fa = (Father) super.clone();
		fa.child = (Child) child.clone();
		return fa;
	}
	
}

   输出结果:在次运行前面的案例,子类也成功拷贝了

   ​ 使用clone方法,如果是深拷贝则使用起来并不是很方便。引用Joshua Bloch的对Cloneable的看法:

最初的 Java 团队做得非常出色,但并非所有的 API 都是完美的。Cloneable是一个弱点,我认为人们应该意识到它的局限性。他不再使用clone除了复制数组。使用clone来复制数组通常是最快的方法。

3.3、使用序列化实现深拷贝

   ​ 什么是序列化? 把对象转换为字节序列的过程称为对象的序列化。

   ​ 什么是反序列化?把字节序列恢复为对象的过程称为对象的反序列化。

   ​ 为什么需要序列化?方便传输、存储,计算机数据传输是以字节为单位的,能处理所有类型的数据。

   ​ 通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深拷贝。

   ​ 注:实现序列化的对象其类必须实现Serializable接口。

public class Child implements Serializable{
	
	private static final long serialVersionUID = 1454911618153740775L;
	
	private String name;
	private int age;
	
	public Child(String name,int age){
		this.name = name;
		this.age = age;
	}
	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 class Father implements Serializable{
	
	private static final long serialVersionUID = -3167743619335060665L;
	
	private String name;
	private int age;
	final String Fina = new String("final");
	private Child child;
	
	public Father(String name ,int age) {
		this.name =name;
		this.age = age;
	}
	
	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 Child getChild() {
		return child;
	}
	public void setChild(Child child) {
		this.child = child;
	}
	
	//深拷贝
	public Father clone() {
		ObjectOutputStream oos = null;
		ObjectInputStream ois = null;
		Father fa = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			oos = new ObjectOutputStream(baos);
			oos.writeObject(this);
			// 将流序列化成对象
			ByteArrayInputStream bais = new ByteArrayInputStream(baos
					.toByteArray());
			ois = new ObjectInputStream(bais);
			fa = (Father) ois.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return fa;
	}
	
}

​    再测试下我们的案例:输出结果和clone的深拷贝一致。

 关注我,学习更多知识!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穆瑾轩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值