java对象克隆
java对象克隆
简介
当我们想要复制一个对象的所有的属性的时候,即创建一个副本,修改副本的中的值时,不会引起原来的对象的值的变化,这时对象克隆非常有用。
我们先来看一个简单的例子:
创建一个MyUser对象:
@Data
public class MyUser {
private String userName;
private Integer userAge;
}
测试:
public static void main(String[] args) {
MyUser myUser1 = new MyUser();
MyUser myUser2 = myUser1;
myUser2.setUserName("张三");
System.out.println("myUser1==myUser2 ? " + (myUser1 == myUser2));
System.out.println("myUser1.equals(myUser2) ? " + myUser1.equals(myUser2) + "===myUser1==" + myUser1.getUserName());
}
说明使用 “=” 赋值对象指向的是同一个引用地址
如何实现对象克隆?
先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和 深克隆(DeepClone)。
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。
一、浅克隆
@Data
public class MyUser implements Cloneable {
private String userName;
private Integer userAge;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
测试:
public static void main(String[] args) {
MyUser myUser1 = new MyUser();
MyUser myUser2 = (MyUser) myUser1.clone();
myUser2.setUserName("张三");
System.out.println("myUser1==myUser2 ? " + (myUser1 == myUser2));
System.out.println("myUser1.equals(myUser2) ? " + myUser1.equals(myUser2) + "===myUser1==" + myUser1.getUserName());
}
你以为这样就完了? 当 MyUser这个对象中还有另外的一个引用类型时,你再试试?
@Data
public class MyUser implements Cloneable {
private String userName;
private Integer userAge;
private UserRole userRole;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
UserRole
@Data
public class UserRole {
private String roleName;
private String des;
}
测试:
public static void main(String[] args) {
UserRole userRole = new UserRole();
userRole.setRoleName("管理员");
MyUser myUser1 = new MyUser();
myUser1.setUserRole(userRole);
MyUser myUser2 = (MyUser) myUser1.clone();
// 可以看到MyUser这个对象实现了Cloneable 使用 “==” 比较发现他们指向了不同的引用地址,返回false
System.out.println("myUser1==myUser2 ? " + (myUser1 == myUser2));
// 而当我们使用MyUser中没有实现Cloneable的UserRole对象用 “==” 比较是时,发现他们所指向的是同一个引用地址,返回true
System.out.println("myUser1.getUserRole()==myUser2.getUserRole()? " + (myUser1.getUserRole()==myUser2.getUserRole()));
// myUser1与myUser2的值进行比较,结果相等,返回true
System.out.println("myUser1.equals(myUser2) ? " + myUser1.equals(myUser2));
System.out.println("myUser1: roleName==" + myUser1.getUserRole().getRoleName());
System.out.println("myUser2: roleName==" + myUser2.getUserRole().getRoleName());
// 因为UserRole始终指向的同一个地址,所以我们这里修改RoleName, myUser1和myUser2中的UserRole都会改变
userRole.setRoleName("保洁阿姨!");
System.out.println("myUser1: roleName==" + myUser1.getUserRole().getRoleName());
System.out.println("myUser2: roleName==" + myUser2.getUserRole().getRoleName());
}
如何实现深度克隆? 当我们使myUser1克隆出myUser2时,我们希望myUser2中的UserRole对象也是一个新的副本,而不是指向和myUser1中的UserRole是同一个地址,这时,我们就需要UserRole同样也实现Cloneable接口,并且在MyUser中重新对userRole赋值。
二、深度克隆
@Data
public class UserRole implements Cloneable{
private String roleName;
private String des;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
@Data
public class MyUser implements Cloneable {
private String userName;
private Integer userAge;
private UserRole userRole;
@Override
public Object clone() {
MyUser myUser = null;
try {
myUser = (MyUser)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
myUser.userRole = (UserRole)userRole.clone(); //深度复制
return myUser;
}
}
再次测试
总结:浅克隆只能克隆对象中的基本类型、不能克隆引用类型,使用深度克隆才能对对象的嵌套对象进行克隆
其他方式实现对象克隆:
一、对象序列化实现对象克隆
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
对象序列化:https://www.cnblogs.com/dw3306/p/9459186.html
@Data
public class MyUser implements Serializable {
private static final long serialVersionUID = 3359612959285631234L;
private String userName;
private Integer userAge;
private UserRole userRole;
/**
* 深度复制方法,需要对象及对象所有的对象(UserRole)属性都实现序列化
* @return
*/
public MyUser myClone() {
MyUser myUser = null;
try {
// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream ois = new ObjectInputStream(inputStream);
myUser = (MyUser) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return myUser;
}
}
@Data
public class UserRole implements Serializable {
private static final long serialVersionUID = 4292032240444317416L;
private String roleName;
private String des;
}
测试
发现和实现Cloneable接口效果一样
二、使用工具包实现对象克隆
通过 org.apache.commons
中的工具类 BeanUtils
和 PropertyUtils
进行对象复制
BeanUtils
提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而PropertyUtils
不支持这个功能,但是速度会更快一些。在实际开发中,BeanUtils
使用更普遍一点,犯错的风险更低一点。