浅析java对象克隆(复制)

什么是克隆

其实很简单,就是复制。对基本数据类型或对象的复制
如果要复制一个基本数据类型变量,很简单:

int a = 10;
int b = a;

但如果是引用数据类型呢?假设有一个学生类,有一个私有属性学号。

class Student{
	private int age;
	
	public int getAge(){
		return age;
	}
	
	public void setAge(int age){
		this.age=age;
	}
}

测试类

public class Test{
	public static void main(String[] args){
		Student stu1 = new Student();
		Student stu2;
		stu1.setAge(13);
		stu2=stu1;
	    System.out.println("stu1.id = "+stu1.getAge());
	    System.out.println("stu2.id = "+stu2.getAge());
	}
}

运行结果

stu1.id=13  
stu2.id=13

乍一看,感觉复制成功了,其实不然,我们更改stu2.id,看看会出现什么。

public class Test{
	public static void main(String[] args){
		Student stu1 = new Student();
		Student stu2 = null;
		
		stu1.setAge(13);
		stu2=stu1;
	    System.out.println("stu1.id = "+stu1.getAge());
	    System.out.println("stu2.id = "+stu2.getAge());
	    
	    stu2.setAge(15);
	    System.out.println("stu1.id = "+stu1.getAge());
	    System.out.println("stu2.id = "+stu2.getAge());
	    
	}
}

运行结果

stu1.id=15
stu2.id=15

这时候是不是疑惑我只更改了stu2的id值啊,为什么stu1的id值也变成15了。其实使用 = 赋值是将对象的引用赋值给左边变量,相当于两个变量是相同的对象,所以总的来看只是对一个对象进行更改。如图
在这里插入图片描述那如何才能进行对象的复制呢?
你知道Obeject类吗,所有类都直接或间接继承该类,Obeject类中有11个方法,它有两个protected修饰的方法,一个是和垃圾回收机制有关的finalize方法,一个就是这里将要用到的clone方法。源码如下:

/**
     * Creates and returns a copy of this object.  The precise meaning
     * of "copy" may depend on the class of the object. The general
     * intent is that, for any object {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. Note that all arrays
     * are considered to implement the interface {@code Cloneable} and that
     * the return type of the {@code clone} method of an array type {@code T[]}
     * is {@code T[]} where T is any reference or primitive type.
     * Otherwise, this method creates a new instance of the class of this
     * object and initializes all its fields with exactly the contents of
     * the corresponding fields of this object, as if by assignment; the
     * contents of the fields are not themselves cloned. Thus, this method
     * performs a "shallow copy" of this object, not a "deep copy" operation.
     * <p>
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
    protected native Object clone() throws CloneNotSupportedException;

你可以看到是native所修饰的,这是由非java语言所实现的一个方法,运行在java虚拟机之上的。要想对一个对象复制,就必须覆盖clone()方法。

如何实现克隆

浅克隆和深克隆的区别就在于是否支持对引用数据类型进行复制

浅克隆

1.被复制的类需要实现Cloneable接口

不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

2.覆盖clone()方法。

class Student implements Cloneable{
	private int age;
	
	public int getAge(){
		return age;
	}
	
	public void setAge(int age){
		this.age=age;
	}
	
	public Object clone(){
		Student stu = null;
		try{
			stu = (Student)super.clone();
		}catch(CloneNotSupportedException e){
			e.printStackTrace();
		}
		return stu;
	}
}
public class Test{
	public static void main(String[] args){
		Student stu1 = new Student();
		Student stu2 = null;

		stu1.setAge(16);
		stu2 = (Student)stu1.clone();

		System.out.println("stu1.id = "+stu1.getAge());
	    System.out.println("stu2.id = "+stu2.getAge());

		stu2.setAge(17);
		System.out.println("stu1.id = "+stu1.getAge());
	    System.out.println("stu2.id = "+stu2.getAge());
	}
}

运行结果

stu1.id = 16
stu2.id = 16
stu1.id = 16
stu2.id = 17

复制成功。

深克隆

1.覆盖clone方法

如果学生类中多一个引用数据类型呢?增加地址类。

class Address{
	private String addr;
	public String getAddr() {  
         return addr;  
    }  
  
    public void setAddr(String add) {  
         this.add = addr;  
    }  
    public Obeject clone(){
    	Address add = null;
    	try{
    		add = (Address)super.clone();
    	}catch(CloneNotException e){
    		e.printStackTrace();
    	}
    	return add;
    }
}

class Student implements Cloneable{
	private int age;
	private Address addr;
	
	public int getAge(){
		return age;
	}
	
	public void setAge(int age){
		this.age=age;
	}
	public String getAddr() {  
         return addr;  
    }  
  
    public void setAddr(String add) {  
         this.add = addr;  
    }  
	public Object clone(){
		Student stu = null;
		try{
			stu = (Student)super.clone();
		}catch(CloneNotSupportedException e){
			e.printStackTrace();
		}
		stu.addr = (Address)addr.clone(); 
		return stu;
	}
}
public class Test{
	public static void main(String[] args){
		Address addr = new Address();  
         addr.setAdd("杭州市");  
         Student stu1 = new Student();  
         stu1.setNumber(123);  
         stu1.setAddr(addr);  
           
         Student stu2 = (Student)stu1.clone();  
           
         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
          
         addr.setAdd("西湖区");  
           
         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
	}
}

运行结果

学生1:123,地址:杭州市  
学生2:123,地址:杭州市  
学生1:123,地址:西湖区  
学生2:123,地址:杭州市

其实就只是在引用数据类里也覆盖clone方法。然后在学生类中的clone方法多写一句赋值语句即可。

2.使用序列化serializable实现深复制

利用了Io流的方式将这个对象写到IO流里面,然后在从IO流里面读取,这样就实现了一个复制,然后实现序列化的这个会将引用的那个对象也一并进行深复制,这样就实现了这个机制,同时在IO里面读取数据的时候还使用了装饰者模式)

package tete;

import java.io.*;
import java.io.Serializable;

public class Test
{
	public static void main(String[] args) throws Exception
	{
		Teacher3 teacher3 = new Teacher3();
		teacher3.setAge(23);
		teacher3.setName("小红");
		
		Student3 student3 = new Student3();
		student3.setAge(50);
		student3.setName("小兰");
		student3.setTeacher3(teacher3);
		
		Student3 ss = (Student3)student3.deepCopt();
		System.out.println(ss.getAge());
		System.out.println(ss.getName());
		
		System.out.println(ss.getTeacher3().getAge());
		System.out.println(ss.getTeacher3().getName());
		
		ss.getTeacher3().setAge(19);
		ss.getTeacher3().setName("小辉");
		
		System.out.println(teacher3.getAge());
		System.out.println(teacher3.getName());
		//虽然上面的已经改了,但是改的是那个复制对象后的那个里面的,然后那个原来的那个里面的并没有改,下面验证:::
		
		System.out.println(ss.getTeacher3().getAge());
		System.out.println(ss.getTeacher3().getName());
		
		
	
		
		
	}
	
 
}
class Teacher3 implements Serializable
{
//  上面的那个警告可以直接消除,除了使用在设置中不显示这个警告,还可以使用下面的这两条语句中的任何一条语句
//	这个serialVersionUID为了让该类别Serializable向后兼容
//	private static final long serialVersionUID = 1L;
//	private static final long serialVersionUID = 8940196742313994740L;
	private int age;
	private String name;
	public int getAge()
	{
		return age;
	}
	public void setAge(int age)
	{
		this.age = age;
	}
	public String getName()
	{
		return name;
	}
	public void setName(String name)
	{
		this.name = name;
	}
}
	class Student3 implements Serializable
	{
		private static final long serialVersionUID = 1L;
		private int age;
		private String name;
		private Teacher3 teacher3;
		public int getAge()
		{
			return age;
		}
		public void setAge(int age)
		{
			this.age = age;
		}
		public String getName()
		{
			return name;
		}
		public void setName(String name)
		{
			this.name = name;
		}
		public Teacher3 getTeacher3()
		{
			return teacher3;
		}
		public void setTeacher3(Teacher3 teacher3)
		{
			this.teacher3 = teacher3;
		}
		//使得序列化student3的时候也会将teacher序列化
		public Object deepCopt()throws Exception
		{
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream  oos = new ObjectOutputStream(bos);
			oos.writeObject(this);
			//将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中
			//有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中
			
			ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bis);
			return ois.readObject();
			//这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆
		}
	}

运行结果

50
小兰
23
小红
23
小红
19
小辉
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值