java对象拷贝 —— 深入解析clone和序列化实现

本文详细介绍了在Java中如何实现对象的深克隆,包括覆盖Object类的clone方法和利用序列化方式。讨论了这两种方法在处理复杂引用类型时的局限性,并展示了如何处理包含可变对象的深拷贝。此外,还解释了克隆的约定、native方法以及Cloneable接口的作用。最后,通过示例代码展示了如何在实际应用中进行深克隆操作。
摘要由CSDN通过智能技术生成
  • 在java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化的方式来实现。
  • 如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
实现对象克隆的两种方式:
  1. 实现Cloneable接口并重写Object类中的clone()方法;
  2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

Native方法简介:
clone 是一个 native 方法,在底层实现的,运行速度快。native 方法是非 Java 语言实现的,是java底层代码,供 Java 程序调用的。

因为 Java 程序是运行在 JVM 虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。clone就是一种对堆栈的操作,一种底层的操作。

关于基本类型 和 几个特殊引用类型

克隆是针对于对象而言的,基本类型(boolean,char,byte,short,int,long,float,double)、已久具备自身克隆的特性。

另外引用类型中,包装类(Boolean,Character,Byte,Short,Integer,Long,Float,Double)、String类、Date类也实现了自身克隆的特性。

//基本类型
int x = 1;
int y = x;
System.out.println(x);	// 1
System.out.println(y);	// 1
x = 2;
System.out.println(x);	// 2
System.out.println(y);	// 1
//包装类
int a = 1; 
Integer x = new Integer(a);
Integer y = x;
System.out.println(x);	// 1
System.out.println(y);	// 1
    	 
a = 2;
System.out.println(x);	// 1
System.out.println(y);	// 1

x = 3;
System.out.println(x);	// 3
System.out.println(y);	// 1

1、clone方法实现对象拷贝

不可变的类永远都不应该提供clone方法,因为它只会激发不必要的克隆。对于永远都不会变化的类,对象的克隆应该用new来创建,而不是clone。

实现Cloneable接口以及clone方法。主类包装了其他的引用类型的其他类,那么其他类必须都要实现Cloneable 接口 以及clone (保护方法) 方法。

Cloneable接口:
实现Cloneable接口的目的,就是为了表示遵循Object规范中clone的约定。实现clone的规范化,但不是绝对的要求,只是提供一个实现clone方法的参考标准,或大家心中约定的良好clone方法的标准,应尽量达到这个标准。

Object规范中clone的约定:

不是绝对的要求,只是一个实现clone方法时应该遵守的规范标准

  • x.clone() != x 返回 true;
  • x.clone().getClass() == x.getClass() 返回 true;
  • x.clone.equals(x) 返回 true,自定义对象应重写equals方法。

·
按照约定,克隆对象与原对象地址地址不同,所属同一个类,两个对象对应实例各字段的值相同,即克隆的对象不依赖于原对象,也就是 深拷贝
·
按照约定,clone返回的对象应该通过 super.clone() 获得。

实现clone方法:
/**
 *克隆对象,引用可变状态的类的克隆方法
 *@return 返回克隆对象
 */
@Override
protected Object clone() {
	Object object = null;
	try {
		object = super.clone();
	} catch (CloneNotSupportedException e) {
		e.printStackTrace();	// 不可能发生
	}
	return object;
}

e.printStackTrace(); 不可能执行,super.clone()必成功,因为就算不管其父类或超类,顶层类Object 也是实现了clone()方法的。

关于CloneNotSupportedException异常:

  • 抛出异常,以指示类Object中的克隆方法已被调用来克隆对象,但对象的类未实现可克隆的
    接口。覆盖克隆方法的应用程序也可以抛出此异常,以指示对象不能或不应该被克隆。
  • Object声明的protected clone虽然可以阻止实现clone()的子类,在子类外部直接调用clone(),但是对静态方法却没有办法,因为静态方法可以直接访问protected的方法,而这样就有可能会出现对对象使用clone(),而实际对象不支持clone(),所以此时就会抛出这个异常。
整体代码:
package pojo;
//此处不继承Cloneable接口也没问题
class Student implements Cloneable {
	
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
	
	/**
	 * 克隆对象,引用可变状态的类的克隆方法
	 * @return 返回克隆对象
	 */
	@Override
	protected Student clone() {
		Student student = null;
		try {
			student = (Student) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace(); // 不可能发生
		}
		return student;
	}
}
package pojo;

class School implements Cloneable{

    private String name;
    private Student student;
    
    public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Student getStudent() {
		return student;
	}

	public void setStudent(Student student) {
		this.student = student;
	}

	@Override
	public String toString() {
		return "School [name=" + name + ", student=" + student + "]";
	}
	
	/**
	*克隆对象,引用可变状态的类的克隆方法
	*@return 返回克隆对象
	*/
	@Override
	protected School clone() {
		School school = null;
	    try {
			school = super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();	// 不可能发生
		}
	    return school;
	}
	
     public static void main(String[] args) {
    	
    	Student student = new Student();
    	student.setName("张三");
    	
    	School school = new School();
    	school.setName("四川大学");
    	school.setStudent(student);
    	
    	// 对象克隆
    	School school2 = (School) school.clone();
        
        school2.setName("重庆大学");
        
        System.out.println(school);
        System.out.println(school2);
    }

}

运行结果:
在这里插入图片描述

clone缺陷分析:

如果克隆对象中包含的域引用了可变的对象,上述这种简单的 clone 实现可能会导致灾难性的后果。

例如 做如下修改:

class School implements Cloneable{

    private String name;
    private Student[] student;	// 学生数组,可变对象
}
public static void main(String[] args) {
    	
    	Student student = new Student();
    	student.setName("张三");
    	Student student2 = new Student();
    	student2.setName("李四");
    	
    	// 创建学生数组对象
    	Student[] stuArray = new Student[] {student, student2};
    	
    	School school = new School();
    	school.setName("四川大学");
    	school.setStudent(stuArray);
    	
    	// 对象克隆
    	School school2 = (School) school.clone();
    	
        school2.setName("重庆大学");
        
        Student student3 = new Student(); 
        student3.setName("杨洋"); 
        Student student4 = new Student();
        student4.setName("李冬梅"); 
        
        // 此时如果直接修改原始实例的可变数组,会破坏克隆对象
        stuArray[0] = student3;
        stuArray[1] = student4;
        
        System.out.println(school);
        System.out.println(school2);
    }

运行结果:
在这里插入图片描述直接修改原始实例的可变数组,克隆对象遭到破坏。

因此,对象中包含了可变的对象,克隆后不能直接修改原始实例的可变对象。
应这样做:

Student[] newStuArray = { student3, student4 };
school2.setStudent(newStuArray);

或者

school2.setStudent(new Student[]{student3,student4});

虽然我们可以在写代码时避开直接修改原始实例,但这两种方法显然违背了我们拷贝对象的初衷,而我们一般也不允许这种可能导致灾难的漏洞存在。 因此,我们有必要拷贝可变对象内的真实信息,而不是可变对象的引用。

做法如下:
/**
 *克隆对象,引用可变状态的类的克隆方法
 *@return 返回克隆对象
 */
@Override
protected School clone() {
	School school = null;
	try {
	    school = (School) super.clone();
	    //增加对可变对象的拷贝
	    school.students = students.clone();
	} catch (CloneNotSupportedException e) {
		e.printStackTrace();	// 不可能发生
	}
	return school;
}

此时再运行直接修改原可变对象的代码:

Student student3 = new Student(); 
student3.setName("杨洋"); 
Student student4 = new Student();
student4.setName("李冬梅");
//直接修改原可变对象实例
stuArray[0] = student3;
stuArray[1] = student4;

运行结果:
在这里插入图片描述此时,已是深度拷贝,也达到了对象拷贝的初衷。

再次扩展讲解Cloneable接口:
主类包装了其他的引用类型的其他类,那么其他类必须都要实现Cloneable 接口 以及clone (保护方法) 方法。
为什么是引用类型?
因为java基本类型间的拷贝就是基于值的拷贝了,即clone的底层实现了。另外包装类、String、数组、Date类等java在其内部已经实现了clone。
以上代码中,会发现,Student类不继承Cloneable接口程序运行也没有问题,

2、序列化方式实现对象拷贝

实现方法:

实现Serializable接口。如果类中存在组合形式的使用,那么每个类都要实现Serializable接口。

自定义对象序列化 cloneObj() 函数。

关于对象序列化这里简单介绍一下:
       序列化即串行化,就是将对象是将对象的状态信息转换为可以存储或传输的形式的过程。把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要自己去通过java把相应的对象写成转换成字节流。一般将实例对象转换为键值对的形式,即JSON类型,与toString输出对象类似:与School [name=四川大学, student=[Student [name=杨洋], Student [name=李冬梅]]]。
       能存储到磁盘的数据是确定的数据,此时实例对象的所有成员数据都是确定的,其中的引用都被追溯后得到一个确定的值,这种形态已经解除了形式上的引用关系,我理解为解引用。
       一般将对象写入字节流完成序列化,再通过读取字节流得到序列化信息,然后进过Object对象流的对象构建,得到一个已经与原对象的可变对象解除了引用关系的新克隆对象。

自定义序列化方法:
/**
*序列化对象
*@return 返回序列化后的对象
*/
private static <T> T CloneObj(T obj) {
	T retobj = null;
	try {
		// 写入流中
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		oos.writeObject(obj);
		// 从流中读取
		ObjectInputStream ios = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
		return (T) ios.readObject();
	} catch (Exception e) {
		e.printStackTrace();
	}
	return retobj;
}
整体代码:
package pojo;

import java.io.Serializable;

class Student implements Serializable {
	
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
	
}
package pojo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


class School implements Serializable {

    private String name;
    private Student student;
    
    public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Student getStudent() {
		return student;
	}

	public void setStudent(Student student) {
		this.student = student;
	}

	@Override
	public String toString() {
		return "School [name=" + name + ", student=" + student + "]";
	}

	
	/**
	 *序列化对象
	 *@return 返回序列化后的对象
	 */
	private static <T> T CloneObj(T obj) {
		T retobj = null;
		try {
			// 写入流中
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(obj);
			// 从流中读取
			ObjectInputStream ios = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
			return (T) ios.readObject();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return retobj;
	}

	
     public static void main(String[] args) {
    	
    	Student student = new Student();
    	student.setName("张三");
    	
    	School school = new School();
    	school.setName("四川大学");
    	school.setStudent(student);
    	
    	// 对象克隆
    	School school2 = CloneObj(school);
        
        school2.setName("重庆大学");
        
        System.out.println(school);
        System.out.println(school2);
    }

}

运行结果:
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Whitemeen太白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值