如何实现对象拷贝
1.答案(第三种可不答)
- 实现Cloneable接口并重写Object类中的clone()方法
- 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
- 通过org.apache.commons中的工具类BeanUtils和PropertyUtils进行对象复制
2.浅克隆和深克隆的区别
- 现有Employee类,其中有成员属性为Person类对象
public class Employee implements Cloneable{ private String name; private int age; private Person person; }
- 当我们进行浅克隆的时候,例如将e1 克隆给 e2 ,这时候输出e1,e2发现是实现了克隆,但是实际上e2和e1中的person对象却还是指向了同一个地址
- 也就是说但我们修改了e1中的person属性,那么e2中的person属性也会被修改
- 而深克隆实现后e1,和e2就是完全独立的两个对象,不会相互干扰
3.实现Cloneable接口实现深克隆和浅克隆
- 首先定义Person类
public class Person { private String name; private int age; private int sex; //下面省略构造器,get set toString方法 }
- 定义Employee类,实现Cloneable接口,重写clone方法
- 这里要注意Employee类中有一个成员属性是Person对象,也就是引用对象
public class Employee implements Cloneable{ private String name; private int age; private Person person; public Employee() { } public Employee(String name, int age, Person person) { this.name = name; this.age = age; this.person = person; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } //下面省略get set toString方法 }
3.1 浅克隆实现
- 首先我们创建employee 实例对象,然后进行克隆复制给employee1
- 分别对employee ,employee1 进行输出发现无任何问题
- 但是当我们对employee中的person成员属性进行修改之后,再分别对employee ,employee1进行输出,发现两者都中的值都修改了!这就是浅克隆
- 浅克隆:在进行克隆时,克隆后的两个对象中的引用属性都是指向同一个地址,也就是employee ,employee1中的person是指向同一个对象地址,一个修改了,另一个也会修改
Person person = new Person("name",10,1); Employee employee = new Employee("fanglei",20,person); System.out.println("employee: "+employee); try { //浅克隆 Employee employee1 = (Employee) employee.clone(); System.out.println("employee1: "+ employee1); //修改employee中person的属性,employee1中的属性也修改了 employee.getPerson().setAge(100); System.out.println("employee: "+employee); System.out.println("employee1: "+ employee1); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
- 输出结果:可以看到两个都修改为了100

3.2 深克隆实现
- 通过实现Clonable接口实现深克隆,那么就要该类中的引用对象属性也要实现Clonable接口并且重写clone方法
- 也就是说Person类也要实现Clonable接口并且重写clone方法
- 并在Employee类的clone方法中调用Person的clone方法进行克隆复制
下图为修改之后的Person类:其实就是实现Clonable接口并且重写clone方法
public class Person implements Cloneable{ private String name; private int age; private int sex; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
下图为Employee类clone方法修改之后:
@Override public Object clone() throws CloneNotSupportedException { Employee employee = null; employee = (Employee) super.clone(); employee.person = (Person) this.person.clone(); return employee; }
再调用刚刚的测试克隆代码:
Person person = new Person("name",10,1); Employee employee = new Employee("fanglei",20,person); System.out.println("employee: "+employee); try { //浅克隆 Employee employee1 = (Employee) employee.clone(); System.out.println("employee1: "+ employee1); //修改employee中person的属性,employee1中的属性也修改了 employee.getPerson().setAge(100); System.out.println("employee: "+employee); System.out.println("employee1: "+ employee1); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
输出结果如下:可以看到一个为10,一个为100

4.实现Serializable接口,实现深克隆
- Employee修改只需要实现Serializable接口
public class Employee implements Serializable{}
- Person类修改也只需要实现Serializable接口
public class Person implements Serializable{}
- 新增克隆工具类
public class CloneUtil { private CloneUtil() { throw new AssertionError(); } /** * Clone. * 调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义, * 这两个基于内存的流只要垃圾回收器清理对象时就能够释放资源,这一点不同于对外部资源(如文件流)的释放。 * * @param obj The object. * @param <T> The type. * @return The cloned object. * @throws Exception The exception. */ public static <T> T clone(T obj) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (T) objectInputStream.readObject(); } }
- 调用测试:
Person p1 = new Person("name",10,1); Employee e1 = new Employee("fanglei",20,p1); System.out.println("e1: "+e1); try { Employee e2 = CloneUtil.clone(e1); System.out.println("e2: "+ e2); e1.getPerson().setAge(100); System.out.println("e1: "+e1); System.out.println("e2: "+ e2); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
- 输出结果:发现的确实现了深克隆

5.org.apache.commons中BeanUtils和PropertyUtils工具类实现深克隆
- BeanUtils提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而PropertyUtils不支持这个功能,但是速度会更快一些。在实际开发中,BeanUtils使用更普遍一点,犯错的风险更低一点。
try { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = new Student(); BeanUtils.copyProperties(stu2, stu1); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }
try { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = new Student(); PropertyUtils.copyProperties(stu2, stu1); catch (NoSuchMethodException e) { e.printStackTrace(); }
