克隆的目的:快速创建一个已有对象的副本。
克隆的步骤:
- 创建一个对象
- 将原有对象的数据导入到新创建的数据中
clone方法首先会判对象是否实现了Cloneable接口,若无则抛出CloneNotSupportedException, 最后会调用internalClone. intervalClone是一个native方法,一般来说native方法的执行效率高于非native方法。
源码:
当某个类要复写clone方法时,要继承Cloneable接口。通常的克隆对象都是通过super.clone()方法来克隆对象。
2.浅克隆(shadow clone)
克隆就是复制一个对象的复本.若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象的reference。
- 实现Cloneable接口的类都具备被拷贝的能力,拷贝在内存中进行,比new生成对象性能明显提升;
- clone方法是Object类的protected方法,也就是在用户编写的代码中不能直接调用。为此必须重写定义clone方法,并声明为public,
- Cloneable接口的出现与接口正常使用没有关系,是一个空接口,它只是一个标记表示一个对象需要克隆,如果一个对象需要克隆,而没有实现Cloneable接口,就会产生一个CloneNotSupportedException异常
- Clone()方法存在与Object对象中,但是其不会将对象的全部属性都拷贝,而是有选择的拷贝
- 基本类型:
- 对象:如果变量是一个实例对象,只拷贝地址引用;
- String字符串:拷贝地址引用,但是修改时,会从字符串池中重新生成新的字符串,原来的保持不变;
- 对于Clone实现对象的拷贝:需要新建大量的对象,工程量大可以使用序列化来实现对象的拷贝
需要clone的对象:
package com.gzu.pyu.thinking.in.java.practice0211; import java.io.Serializable; public class Student implements Cloneable ,Serializable { public String name; public int age; public Address address; public Student() { } public Student(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } 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 public Object clone() { Student student=null; try { //淺拷貝 student= (Student)super.clone(); } catch (CloneNotSupportedException e) { return new Student(); } //支持深度克隆 student.address=address.clone(); return student; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Student{"); sb.append("name='").append(name).append('\''); sb.append(", age=").append(age); sb.append(", address=").append(address); sb.append('}'); return sb.toString(); } } class Address implements Cloneable,Serializable{ String city; String town; public Address() { } public Address(String city, String town) { this.city = city; this.town = town; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getTown() { return town; } public void setTown(String town) { this.town = town; } @Override public Address clone() { Address address=null; try { address= (Address) super.clone(); } catch (CloneNotSupportedException e) { return new Address(); } return address; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Address{"); sb.append("city='").append(city).append('\''); sb.append(", town='").append(town).append('\''); sb.append('}'); return sb.toString(); } }
测试方法如下:
package com.gzu.pyu.thinking.in.java.practice0211; import com.gzu.pyu.thinking.in.java.utils.CloneUtils; public class TestMain { public static void main(String ... args){ Address address=new Address("ShengZheng","LongGang"); //将address指向的区域赋值给addressNew,使得addressNew和address同时指向了同一个区域 Address addressNew=address; //address指向的内容改变了,addressNew指向的内容也跟着改变 System.out.println(address==addressNew);// true address.setCity("GuiYang"); System.out.println(address); // Address{city='GuiYang', town='LongGang'} System.out.println(addressNew); // Address{city='GuiYang', town='LongGang'} //address進行克隆,將address進行了复制了,产生了一个新的对象 Address addressClone = address.clone(); System.out.println(address==addressClone);// false address.setTown("HuaXi"); System.out.println(address); // Address{city='GuiYang', town='HuaXi'} System.out.println(addressClone); // Address{city='GuiYang', town='LongGang'} //在内存中通过字节流的拷贝是比较容易的,把木对象写入到一个字节流中,再从字节流中读出来 Address addr = CloneUtils.clone(address); System.out.println(address==addr);// false addr.setCity("PanZhou"); System.out.println(address); // Address{city='GuiYang', town='HuaXi'} System.out.println(addr); // Address{city='PanZhou', town='HuaXi'} } }
二、使用序列化来实现对象的拷贝
- 在内存中通过字节流的拷贝是比较容易的,把木对象写入到一个字节流中,再从字节流中毒出来,这样可以创建一个新的对象,且新对象与木对象不存在引用共享的问题,实现的对象的深拷贝
package com.gzu.pyu.thinking.in.java.utils; import java.io.*; /** * 在内存中通过字节流的拷贝是比较容易的,把木对象写入到一个字节流中,再从字节流中读出来, * 这样可以创建一个新的对象,且新对象与木对象不存在引用共享的问题,实现的对象的深拷贝 */ public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj){ T cloneObj = null; //写入字节流 try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new ObjectOutputStream(out); obs.writeObject(obj); obs.close(); //分配内存,写入原始对象,生成新对象 ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(ios); //返回生成的新对象 cloneObj = (T) ois.readObject(); ois.close(); }catch(IOException e){ e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return cloneObj; } }