Java中的拷贝分为引用拷贝和对象拷贝
1、引用拷贝(浅拷贝)
引用拷贝:只会生成一个新的对象引用地址,但两个对峙最终指向的还是同一个对象
代码示例:
java
// 定义的测试对象
@Data
public class User {
private String name;
private String age;
private String address;
public User(String name, String age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
// 测试类
@Test
void testCopy() {
User userA = new User("john", "23", "西斗门路");
System.out.println(userA);
User userB = userA;
userB.setName("matt");
System.out.println("userA:" + userA);
System.out.println("userB:" + userB);
}
/** 执行结果
* User(name=john, age=23, address=西斗门路)
* userA:User(name=matt, age=23, address=西斗门路)
* userB:User(name=matt, age=23, address=西斗门路)
*/
可以看到,在修改userB的属性后,userA的属性也一同被修改了,说明此时二者的引用对象都是同一个。
2、对象拷贝(深拷贝/浅拷贝)
对象拷贝:会重新生成一个对象,新生成的对象和之前的对象没有任何关联。
对象拷贝中,区分为两类:对象浅拷贝、对象深拷贝
2.1、对象浅拷贝
在克隆操作中国,只复制对象本身以及对象内部的基本数据类的属性,而不会复制对象内部的引用类型的属性。
浅拷贝仅仅创建了一个新的对象,该对象与原对象的引用类型属性还是指向同一地址,当应用类型发生修改时,二者都会变更。
在浅拷贝中,新对象和原始对象指向同一块内存区域,因此对其中一个对象进行修改可能会影响到另一个对象。
想要实现对象浅拷贝,可以通过实现Cloneable接口重写其clone方法
代码示例:
java
// 定义的测试对象
@Data
public class User implements Cloneable{
private String name;
private String age;
private String address;
private Father father;
public User(String name, String age, String address, Father father) {
this.name = name;
this.age = age;
this.address = address;
this.father = father;
}
public User copyUser() throws CloneNotSupportedException {
return (User) clone();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Data
public class Father {
private String name;
public Father(String name) {
this.name = name;
}
}
@Test
void testCopy() {
Father fatherA = new Father("William");
User userA = new User("john", "23", "西斗门路", fatherA);
System.out.println(userA);
User userB = null;
try {
userB = userA.copyUser();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
userB.setName("matt");
userB.getFather().setName("Billion");
System.out.println("userA:" + userA);
System.out.println("userB:" + userB);
}
/**
* 执行结果
* User(name=john, age=23, address=西斗门路, father=Father(name=William))
* userA:User(name=john, age=23, address=西斗门路, father=Father(name=Billion))
* userB:User(name=matt, age=23, address=西斗门路, father=Father(name=Billion))
*/
2.2、对象深拷贝
- 深拷贝指的是在复制对象的过程中,除了复制对象及其基本类型的属性时,还要递归的复制对象内部的引用类型的属性。
- 深拷贝会创建一个完全独立的对象, 新对象和原对象不会产生任何关联,各自的属性值修改互不影响
实现有两个方案:
1、实现Serializable接口,通过序列化方式进行克隆(下面示例使用方式)
2、重写Cloneable的clone方法,即对原对象的引用类型的属性二次调用clone方法进行克隆
代码示例:
java
// 创建对象,注意都需要实现Serializable接口
@Data
public class User implements Serializable {
private String name;
private String age;
private String address;
private Father father;
public User(String name, String age, String address, Father father) {
this.name = name;
this.age = age;
this.address = address;
this.father = father;
}
}
@Data
public class Father implements Serializable {
private String name;
public Father(String name) {
this.name = name;
}
}
// 通过序列化实现深拷贝的方法
public static <T extends Serializable> T deepCopy(T object) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
// 序列化对象
objectOutputStream.writeObject(object);
objectOutputStream.flush();
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
// 反序列化对象,实现深拷贝
return (T) objectInputStream.readObject();
}
} catch (IOException | ClassNotFoundException e) {
// 处理n异常
e.printStackTrace();
}
// 返回null,表示深拷贝失败
return null;
}
// 测试
@Test
void testCopy() {
Father fatherA = new Father("William");
User userA = new User("john", "23", "西斗门路", fatherA);
System.out.println(userA);
User userB = deepCopy(userA);
userB.setName("matt");
userB.getFather().setName("Billion");
System.out.println("userA:" + userA);
System.out.println("userB:" + userB);
}
/**
* 执行结果
* User(name=john, age=23, address=西斗门路, father=Father(name=William))
* userA:User(name=john, age=23, address=西斗门路, father=Father(name=William))
* userB:User(name=matt, age=23, address=西斗门路, father=Father(name=Billion))
*/
附带知识:使用try-with-resources:在Java 7及更高版本中,可以使用try-with-resources语句来自动关闭实现了Closeable接口的资源。在这段代码中,ByteArrayOutputStream、ObjectOutputStream、ByteArrayInputStream和ObjectInputStream都实现了Closeable接口,因此可以使用try-with-resources来自动关闭这些资源,而无需手动调用close方法。