目录
1.浅拷贝
浅拷贝时会创建一个新对象,这个对象有着原始对象属性值的一份精准拷贝。
若属性是基本类型,拷贝的就是基本类型的值;
若属性是引用类型,拷贝的就是内存地址;因此其中一个对象改变了这个地址,就会影响到另一个对象。
测试:
a.创建一个User实体类,包含两个属性:name,clothes。并实现了Cloneable接口,重写了clone方法。
package clonePackage;
import java.util.List;
public class User implements Cloneable{
private String name;//姓名
private int age;//年龄
private List<String> clothes;//衣服
public User() {}
public User(String name, int age, List<String> clothes) {
this.name = name;
this.age = age;
this.clothes = clothes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getClothes() {
return clothes;
}
public void setClothes(List<String> clothes) {
this.clothes = clothes;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", clothes=" + clothes +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
b.创建一个测试类进行测试
package clonePackage;
import java.util.ArrayList;
import java.util.List;
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
List<String> clothes = new ArrayList<>();
clothes.add("夹克");
clothes.add("牛仔裤");
User user = new User("张三",18, clothes);
User userClone = (User) user.clone();
System.out.println(user);
System.out.println(userClone);
user.setName("李四");
user.setAge(24);
clothes.add("羽绒服");
clothes.add("棉裤");
System.out.println(user);
System.out.println(userClone);
}
}
运行结果如下:
分析:
1.userClone 克隆了user的基本类型的值(age),而对于引用类型的clothes其实只是克隆了其地址。当改变了user的age和clothes时,发现userClone的age没有发生变化,但是clothes却变了。
2.在虚拟机中,他们的存储如下图:
疑问:
String 类型也是引用类型,为什么user的name属性被修改后,而userClone的name没有发生变化呢?
解答:
String类型比较特殊,它没有实现Cloneable接口,因此无法克隆,只能传递引用。在clone()后,指向的值为常量。克隆出来的对象改变他的值,实际上是改变了克隆出来对象String类型成员的指向,不会影响被克隆对象的值及其指向。
因此String在拷贝的时候就表现出了“深拷贝”的特点;实际上String作为不可更改的类,在new赋值的时候,就已经创建了一个新的对象;
2.深拷贝-Cloneable
修改上述的User类中的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
List<String> list = new ArrayList<>(this.clothes);
user.clothes = list;
return user;
}
此时在clone的时候new了一个新的list,这样的话userClone就会指向新的地址。标号1的指向将不存在,而是在堆中开辟了新的空间(标号2)。
此种改法会存在问题:
若是出现多个复杂类型的嵌套,或者User类中有其他类的引用,其他类中又存在复杂类型和别的类......,岂不是每个类都要实现Cloneable接口,并重写clone方法,超级麻烦。
3.深拷贝-序列化
1.User类实现Serializable接口
package clonePackage;
import java.io.Serializable;
import java.util.List;
public class User implements Serializable {
private String name;//姓名
private int age;//年龄
private List<String> clothes;//衣服
public User() {}
public User(String name, int age, List<String> clothes) {
this.name = name;
this.age = age;
this.clothes = clothes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getClothes() {
return clothes;
}
public void setClothes(List<String> clothes) {
this.clothes = clothes;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", clothes=" + clothes +
'}';
}
}
2.创建一个深度拷贝的工具类
package clonePackage;
import java.io.*;
public class DeepCopyUtil {
public static <T extends Serializable> T deepClone(T obj) {
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
测试如下: