方式一:使用setter方法
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() + " " + stu1.hashCode());
System.out.println("学生2: "+stu2.getNumber() + " " + stu2.hashCode());
stu2.setNumber(54321);
System.out.println("学生1: "+stu1.getNumber() + " " + stu1.hashCode());
System.out.println("学生2: "+stu2.getNumber() + " " + stu2.hashCode());
}
}
运行结果:
学生1: 12345 1580066828
学生2: 12345 491044090
学生1: 12345 1580066828
学生2: 54321 491044090
从程序的运行结果可以看出,学生2修改值类型变量(如int)number后,自身number值发生改变,但并不影响学生1
深克隆: 将原型对象的所有引用对象也复制一份给克隆对象
public class Student implements Cloneable {
private int number;
private Address add;
public void setAdd(Address add) {
this.add = add;
}
private Address getAdd(){
return this.add;
}
public void setNumber(int number){
this.number = number;
}
public int getNumber(){
return this.number;
}
@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() + ", " + stu1.hashCode());
System.out.println("学生2: " + stu2.getNumber() + ",地址: " + stu2.getAdd().getAdd() + ", " + stu2.hashCode());
stu2.setNumber(54321);
add.setAdd("杭州市");
System.out.println("学生1: " + stu1.getNumber() + ",地址: " + stu1.getAdd().getAdd() + ", " + stu1.hashCode());
System.out.println("学生2: " + stu2.getNumber() + ",地址: " + stu2.getAdd().getAdd() + ", " + stu1.hashCode());
}
}
class Address implements Cloneable {
public String add;
@Override
protected Object clone() {
Address add = null;
try {
add = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return add;
}
public void setAdd(String add) {
this.add = add;
}
public String getAdd(){
return this.add;
}
}
运行结果:
学生1: 12345,地址: 北京市, 1580066828
学生2: 12345,地址: 北京市, 491044090
学生1: 12345,地址: 杭州市, 1580066828
学生2: 54321,地址: 北京市, 1580066828
当去掉上述代码中的stu.add = (Address) add.clone();
时,运行结果如下。可以看出引用类型对象Address add并未复制到克隆对象中
学生1: 12345,地址: 北京市, 1580066828
学生2: 12345,地址: 北京市, 491044090
学生1: 12345,地址: 杭州市, 1580066828
学生2: 54321,地址: 杭州市, 1580066828
方式三:实现Serializable接口
通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
Car类
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;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
Person类
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;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
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;
}
}
CloneUtil类
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 extends Serializable> 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();
}
}
CloneTest类
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);
System.out.println("person car before:" + person.getCar().getBrand() + " " + person.hashCode() + " " + person.getCar().hashCode());
System.out.println("person1 car before:" + person1.getCar().getBrand() + " " + person1.hashCode() + " " + person1.getCar().hashCode());
person1.getCar().setBrand("BYD");
System.out.println("person car after:" + person.getCar().getBrand() + " " + person.hashCode() + " " + person.getCar().hashCode());
System.out.println("person1 car after:" + person1.getCar().getBrand() + " " + person1.hashCode() + " " + person1.getCar().hashCode());
}
}
运行结果
person car before:BMW 895328852 1304836502
person1 car before:BMW 824909230 122883338
person car after:BMW 895328852 1304836502
person1 car after:BYD 824909230 122883338
备注1: 修改克隆的Person 对象person1 关联的 汽车对象 的品牌属性,原来的Person对象person关联的汽车不会受到任何影响,因为在克隆Person对象时其关联的Car对象也被克隆了
备注2: 基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过范型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译期完成的,不是在运行时抛出异常,这种方案明显优于使用Object类的clone方法克隆对象
方式四:通过org.apache.commons中的工具类BeanUtils和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();
}
使用PropertyUtils
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使用更普遍,犯错的风险更低