深拷贝和浅拷贝主要是针对对象的属性是对象(引用类型)
对于引用类型和基本类型看这篇:基本类型和引用类型
拷贝
拷贝,就是赋值。把一个变量赋给另外一个变量,就是把变量的内容进行拷贝。把一个对象的值赋给另外一个对象,就是把一个对象拷贝一份。
基本类没有问题,因为,基本类型赋值时,赋的是数据(所以,不存在深拷贝和浅拷贝的问题)。
如下:
int num1 = 10;
int num2 = num1;
// 如果要改变num2的值,num1的值不会改变
num2 = 11;
System.out.println(num1);
System.out.println(num2);
输出
10
11
引用类型有问题,因为,引用类型赋值时,赋的值地址(就是引用类型变量在内存中保存的内容)。
如下:
int[] ins1 = new int[]{1,2,3};
//这就是一个最简单的浅拷贝
int[] ins2 = ins1;
//如果要改变ins2所引用的数据:ins2[0]=0时,那么ins1[0]的值也是0。
//原因就是 ins1和ins2引用了同一块内存区域。
ins2[0] = 0;
System.out.println(ins1[0]);
System.out.println(ins2[0]);
输出:
0
0
这是最简单的浅拷贝,因为,只是把ins1的地址拷贝的一份给了ins2,并没有把ins1的数据拷贝一份。所以,拷贝的深度不够。
浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
原型对象和复制出来的新对象,他们的引用类型新的属性如果地址值相同则是浅拷贝。
示例代码
public class Test {
@SneakyThrows
public static void main(String[] args) {
User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
User user2 = (User) user1.clone();
user2.setName("lisi");
user2.getBooks()[0] = "斯国一";
System.out.println(user1);
System.out.println(user2);
}
}
@Data
class User implements Cloneable{
private String name;
private String[] books;
public User(String name, String[] books) {
this.name = name;
this.books = books;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
输出
User(name=zhangsan, books=[斯国一, 水浒, 红楼梦, 西游记])
User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])
内存图
user1中有一个对象books,指向了”三国”,”水浒”,”红楼梦”,”西游记”,浅拷贝了一个user2,他包含的books依然指向”三国”,”水浒”,”红楼梦”,”西游记”,此时通过user2去修改了books里的三国为斯国一,那么user1的引用自然也变成了斯国一。
深拷贝
原型对象和复制出来的新对象,他们的引用类型新的属性如果地址值不相同则是深拷贝。
示例代码
public class Test {
@SneakyThrows
public static void main(String[] args) {
User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
User user2 = (User) user1.clone();
user2.setName("lisi");
user2.getBooks()[0] = "斯国一";
System.out.println(user1);
System.out.println(user2);
}
}
@Data
class User implements Cloneable{
private String name;
private String[] books;
public User(String name, String[] books) {
this.name = name;
this.books = books;
}
@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.books = user.getBooks().clone();
return user;
}
}
输出
User(name=zhangsan, books=[三国, 水浒, 红楼梦, 西游记])
User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])
内存图
user1中有一个对象books,指向了”三国”,”水浒”,”红楼梦”,”西游记”,深拷贝了一个user2,同时把他里面的对象books指向也复制了一份”三国”,”水浒”,”红楼梦”,”西游记”,所以user1和user2指向各自的”三国”,”水浒”,”红楼梦”,”西游记”,此时通过user2将books中的三国改成了斯国一,user1中引用的books不变化。
如何实现浅拷贝
1. 通过clone方法
如上面浅拷贝示例所示,只需要给原型对象(也就是被复制的对象)实现一个Cloneable接口(是一个空接口,是一个标记接口),然后调用super.clone方法产生新的对象,就可以了。
2. 通过构造方法实现浅拷贝
public class Test {
@SneakyThrows
public static void main(String[] args) {
User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
User user2 = new User(user1);
user2.setName("lisi");
user2.getBooks()[0] = "斯国一";
System.out.println(user1);
System.out.println(user2);
}
}
@Data
class User{
private String name;
private String[] books;
public User(String name, String[] books) {
this.name = name;
this.books = books;
}
// 通过构造方法实现浅拷贝
public User(User user) {
this.name = user.name;
this.books = user.books;
}
}
User(name=zhangsan, books=[斯国一, 水浒, 红楼梦, 西游记])
User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])
如何实现深拷贝
1. 通过对对象内的引用类型再次调用clone方法
如上面深拷贝示例代码,通过对对象内的引用类型再次调用clone方法实现深拷贝。
2. 通过序列化反序列化实现深拷贝
拷贝的对象实现序列化接口,然后通过序列化与反序列化实现深拷贝。
public class Test {
@SneakyThrows
public static void main(String[] args) {
User user1 = new User("zhangsan",new String[]{"三国","水浒","红楼梦","西游记"});
User user2 = (User) user1.deepClone();
user2.setName("lisi");
user2.getBooks()[0] = "斯国一";
System.out.println(user1);
System.out.println(user2);
}
}
@Data
class User implements Serializable {
private String name;
private String[] books;
public User(String name, String[] books) {
this.name = name;
this.books = books;
}
//深度拷贝
public Object deepClone() throws Exception{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
输出
User(name=zhangsan, books=[三国, 水浒, 红楼梦, 西游记])
User(name=lisi, books=[斯国一, 水浒, 红楼梦, 西游记])