Java实现对象克隆的方法
1、Java实现克隆有两种形式
- 浅拷贝
- 深拷贝
在Java中吗,我们说两个对象是否相等通常有两层含义:
- 对象的内容是否相等,通常使用到对象的
equals(Object o)
函数; - 引用的地址是否相同,使用运算符
==
比较即可。
当两个对象通过赋值符号 =
赋值时,表明这两个对象指向了内存中同一个地址,所以改变其中一个对象的内容,也就间接地改变了另一个对象的内容。有时候,我们需要从一个已经存在的对象重新拷贝一份出来,并且不仅这两个对象内容相等,在内存中存在两个独立的存储地址,互不影响,这时,就需要用到 Java 中的克隆机制。
2、Cloneable
通过 Cloneable 接口可以很轻松地实现 Java 对象的克隆,只需要 implements Cloneable 并实现 Object 的 clone() 方法即可。
public class User implements Cloneable{
private String username;
private String password;
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
User user = (User) obj;
if (username.equals(user.username) && password.equals(user.password)) {
return true;
}
return false;
}
}
注意这里对象实现的是 Object 类的 clone() 方法,因为 Cloneable 是一个空接口:
package java.lang;
public interface Cloneable {
}
从源码注释中可以看出,需要实现 Object 类中的 clone() 方法(注意:clone() 函数是一个 native 方法,同时抛出了一个异常)
protected native Object clone() throws CloneNotSupportedException;
从 clone() 函数的注释中能够看出对象与克隆对象之间的关系,测试代码如下(注意:我们在 User 对象中重写了 equals() 函数)
public static void main(String[] args) throws CloneNotSupportedException{
User userOne, userTwo, userThree;
userOne = new User("username", "password");
userTwo = userOne;
userThree = (User) userOne.clone();
System.out.println(userTwo==userOne); //true
System.out.println(userTwo.equals(userOne)); //true
System.out.println(userThree==userOne); //false
System.out.println(userThree.equals(userOne)); //true
}
测试结果显示,通过 clone() 函数,我们成功地从 userOne 对象中克隆出了一份独立的 userThree 对象。
3、浅拷贝和深拷贝的区别
2.1 浅拷贝
1、实现Cloneable接口,重写Object中的clone()方法
谈此之前,我们先看一个例子,定义一个名为 Company 的类,并添加一个类型为 User 的成员变量:
public class Company implements Cloneable{
private User user;
private String address;
public Company(User user, String address) {
super();
this.user = user;
this.address = address;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
Company company = (Company) obj;
if (user.equals(company.getUser()) && address.equals(company.address)) {
return true;
}
return false;
}
}
测试代码及测试结果如下:
public static void main(String[] args) throws CloneNotSupportedException{
Company companyOne, companyTwo, companyThree;
companyOne = new Company(new User("username", "password"), "上海市");
companyTwo = companyOne;
companyThree = (Company) companyOne.clone();
System.out.println(companyTwo==companyOne); //true
System.out.println(companyTwo.equals(companyOne)); //true
System.out.println(companyThree==companyOne); //false
System.out.println(companyThree.equals(companyOne)); //true
System.out.println(companyThree.getUser()==companyOne.getUser()); //true ? 这里为什么不是false呢
System.out.println(companyThree.getUser().equals(companyOne.getUser())); //true
}
问题来了,companyThree 与 companyOne 中的 User 是同一个对象!也就是说 companyThree 只是克隆了 companyOne 的基本数据类型的数据,而对于引用类型的数据没有进行深度的克隆。也就是俗称的浅克隆。
浅克隆:顾名思义,就是很表层的克隆,只克隆对象自身的引用地址;
深克隆:也称“N层克隆”,克隆对象自身以及对象所包含的引用类型对象的引用地址。
这里需要注意的是,对于基本数据类型(primitive)和使用常量池方式创建的String 类型,都会针对原值克隆,所以不存在引用地址一说。当然不包括他们对应的包装类。
2.2 深拷贝
1、递归调用clone()方法
所以使用深克隆就可以解决上述 Company 对象克隆过后两个 user 对象的引用地址相同的问题。我们修改一下 Company 类的 clone() 函数。
@Override
protected Object clone() throws CloneNotSupportedException {
Company company = (Company) super.clone();
company.user = (User) company.getUser().clone();
return company;
}
再运行测试代码,就能得到 companyThree.getUser()==companyOne.getUser()
为 false 的结果了。从实现过程来说,递归克隆存在克隆过程多且复杂的缺点。
2、通过序列化方式
public class Text implements Serializable{
private static final long serialVersionUID = 8723901148964L;
private int age;
private Name name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
public Object myClone(){
Text text=null;
ByteArrayOutputStream bos=new ByteArrayOutputStream();
try {
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
text=(Text)ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return text;
}
}
class Name implements Serializable {
private static final long serialVersionUID = 872390113109L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
结果分析:
采用深克隆能有效隔离源对象与克隆对象的联系。
从实现过程来说,递归克隆存在克隆过程多且复杂的缺点,所以建议采用序列化的方式进行深克隆。
总结
Java对象克隆共有两种形式,三种方法
-
浅拷贝
- 调用clone方法
-
深拷贝
-
递归调用clone方法
-
序列化对象
-
三种方法之间互有优缺点,具体采用要根据实际情况。