Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = new Student();
stu2.setNumber(stu1.getNumber());
Cloneable
接口并重写Object
类中的clone()
方法浅克隆:当对象被复制时,只复制对象本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
public class Student implements Cloneable {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
//实现clone接口
@Override
public Object clone() {
Student stu = null;
try {
//浅克隆
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
//测试克隆
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student) stu1.clone();
System.out.println("学生1: "+stu1.getNumber());
System.out.println("学生2: "+stu2.getNumber());
stu2.setNumber(54321);
System.out.println("学生1: "+stu1.getNumber());
System.out.println("学生2: "+stu2.getNumber());
}
}
结果:
学生1: 12345
学生2: 12345
学生1: 12345
学生2: 54321
深克隆:将原型对象的所有引用对象也复制一份给克隆对象。
public class Student implements Cloneable {
private int number;
private Address add;
//get、set方法
@Override
public Object clone() {
Student stu = null;
try {
//浅复制
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//深度复制
stu.add = (Address) add.clone();
return stu;
}
public static void main(String[] args) {
Address add = new Address();
add.setAdd("北京市");
Student stu1 = new Student();
stu1.setNumber(12345);
stu1.setAdd(add);
Student stu2 = (Student) stu1.clone();
System.out.println("学生1: " + stu1.getNumber() + ",地址: " + stu1.getAdd().getAdd());
System.out.println("学生2: " + stu2.getNumber() + ",地址: " + stu2.getAdd().getAdd());
add.setAdd("杭州市");
System.out.println("学生1: " + stu1.getNumber() + ",地址: " + stu1.getAdd().getAdd());
System.out.println("学生2: " + stu2.getNumber() + ",地址: " + stu2.getAdd().getAdd());
}
}
class Address implements Cloneable {
private String add;
//get、set方法
@Override
protected Object clone() {
Address add = null;
try {
add = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return add;
}
}
Serializable
接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆import java.io.Serializable;
public class Car implements Serializable {
private static final long serialVersionUID = 6880783148707693267L;
//品牌
private String brand;
//最高时速
private int maxSpeed;
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
//get、set方法
//重写toString方法
}
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 7592930394427200495L;
//姓名
private String name;
//年龄
private int age;
//座驾
private Car car;
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
//get、set方法
//重写toString方法
}
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class CloneUtil {
public static T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
}
}
调用ByteArrayOutputStream
或ByteArrayInputStream
对象的close方法没有任何意义。这两个基于内存的流只要垃圾收集器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放。
public class CloneTest {
public static void main(String[] args) throws Exception {
Person person = new Person("lisi", 18, new Car("BMW", 300));
Person person1 = CloneUtil.clone(person);
person1.getCar().setBrand("BYD");
}
}
修改克隆的Person对象person1关联的汽车对象的品牌属性,原来的Person对象person关联的汽车不会受到任何影响,因为在克隆Person对象时其关联的汽车对象也被克隆了。
基于序列化和反序列化实现的克隆不仅仅时深度克隆,更重要的是通过范型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译期完成的,不是在运行时抛出异常,这种方案明显优于使用Object类的clone方法克隆对象。
org.apache.commons
中的工具类BeanUtils
和PropertyUtils
进行对象复制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();
}
BeanUtils
提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而PropertyUtils
不支持这个功能,但是速度会更快一些。在实际开发中,BeanUtils
使用更普遍一点,犯错的风险更低一点。