1、Java中拷贝的概念
在Java语言中,拷贝一个对象时,有浅拷贝与深拷贝两种
浅拷贝:只拷贝源对象的地址,所以新对象与老对象共用一个地址,当该地址变化时,两个对象也会随之改变。
深拷贝:拷贝对象的所有值,即使源对象发生任何改变,拷贝的值也不会变化。
在User类的基础上,介绍两种浅拷贝案列
User类:
@Data
public class User {
private String name;
private Integer age;
}
案列①:普通对象的浅拷贝
package com.shuizhu.study;
//浅拷贝案例1
public class Study01 {
public static void main(String[] args) {
User user1 = new User();
user1.setName("张三");
user1.setAge(18);
User user2 = user1;
System.out.println("user1未改变前,user2的名字为:" + user2.getName());
user1.setName("李四");
System.out.println("user1未改变前,user2的名字为:" + user2.getName());
}
}
结果:改变user1后,user2的值也随之变化
案列②:List浅拷贝(这也是我们平时项目中,经常遇到的情况)
package com.shuizhu.study;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
//Java浅拷贝案列2
public class Study02 {
public static void main(String[] args) {
List<User> list1 = new ArrayList<>();
User user1 = new User();
user1.setName("张三");
user1.setAge(18);
User user2 = new User();
user2.setName("李四");
user2.setAge(19);
list1.add(user1);
list1.add(user2);
//TODO 以下是开发中,经常发生的浅拷贝
//方式1:通过new ArrayList方式,把list01拷贝给list02
List<User> list2 = new ArrayList<>(list1);
System.out.println("list1未改变前,list2的结果为:" + list2);
//方式2:通过addAll方法,把list01拷贝给list02
List<User> list3 = new ArrayList<>();
list3.addAll(list1);
System.out.println("list1未改变前,list3的结果为:" + list3);
//方式3:通过stream流的方式,把list01拷贝给list02
List<User> list4 = list1.stream().collect(Collectors.toList());
System.out.println("list1未改变前,list4的结果为:" + list4);
//改变list1集合中的user1对象
System.out.println("--------------------------------------------");
user1.setName("老六");
user1.setAge(78);
System.out.println("list1改变后,list2的结果为:" + list2);
System.out.println("list1改变后,list3的结果为:" + list3);
System.out.println("list1改变后,list4的结果为:" + list4);
}
}
结果:对List的3种拷贝,其实都是浅拷贝,当源集合中对象发生改变时,新的List也会随之变化
2、常见的深拷贝方式
- 构造函数方式(new的方式)
- 重写clone方法
- Apache Commons Lang序列化
- Gson序列化
- Jackson序列化
2.1、构造函数方式
这种方式就是创建一个新的对象,然后通过源对象的get方法与新对象set方法,把源对象的值复制新对象,这里就不再演示了。
缺点:在拷贝的对象数量较少时,可以使用,但是对象数量过多时,会大大增加系统开销,开发中应避免使用。
2.2、重写clone方法
步骤:
1>需要拷贝对象的类,去实现Cloneable接口
2>重写clone方法
3>使用"对象.clone()"的方式进行拷贝
根据上面的案列,进行对应的改造:
首先是User实体类 ,如下:
@Data
public class User implements Cloneable{
private String name;
private Integer age;
@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
改造案列①:
package com.shuizhu.study;
//Java深拷贝案列
public class Study03 {
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User();
user1.setName("张三");
user1.setAge(18);
User user2 = user1.clone();
System.out.println("user1未改变前,user2的名字为:" + user2.getName());
user1.setName("李四");
System.out.println("user1未改变前,user2的名字为:" + user2.getName());
}
}
结果:当user1改变后,user2的值不会改变
改造案列2:
package com.shuizhu.study;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
//Java深拷贝案列
public class Study04 {
public static void main(String[] args) {
List<User> list1 = new ArrayList<>();
User user1 = new User();
user1.setName("张三");
user1.setAge(18);
User user2 = new User();
user2.setName("李四");
user2.setAge(19);
list1.add(user1);
list1.add(user2);
//TODO 以下是开发中,经常发生的浅拷贝
//方式1:通过new ArrayList方式,把list01拷贝给list02
List<User> list2 = new ArrayList<>();
list1.forEach(user->{
try {
list2.add(user.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
});
System.out.println("list1未改变前,list2的结果为:" + list2);
//改变list1集合中的user1对象
System.out.println("--------------------------------------------");
user1.setName("老六");
user1.setAge(78);
System.out.println("list1改变后,list2的结果为:" + list2);
}
}
结果:list1中的每个对象通过clone()添加list2中,当list1中的对象改变时,list2不会改变
2.3 、Apache Commons Lang序列化