1. 前言
Java中的数据类型分为基本数据类型(值类型)和引用数据类型,基本数据类型包括byte、short、int、long、float、double、boolean、char
等简单数据类型,引用类型包括:类、接口、数组等复杂类型。
根据数据类型的不同,在进行数据值拷贝的适合,如果是基本数据类型,复制的是属性值,如果是引用数据类型,比如对象,复制的内容可能是属性对应的内存引用地址。
因此,在Java中对于引用数据类型,也分为浅拷贝(浅克隆) 和 深拷贝(深克隆) ,区别如下:
- 浅拷贝:将原对象或原数组的引用直接赋给新对象或新数组,新对象只是原对象的一个引用,也就是说不管新对象还是原对象都是引用同一个对象
- 深拷贝:创建一个新的对象或数组,将原对象的各项属性的值拷贝过来,是”值”而不是”引用”,两者对象是不一样的。
2. 案例实践
2.1 浅拷贝
首先新建两个对象,其中User
关联Account
对象,内容如下:
1 | public class Account { |
1 | public class User { |
使用spring BeanUtils
工具进行对象属性赋值
1 | public static void main(String[] args) { |
输出结果如下:
1 | 修改嵌套对象属性前的结果: User{userId=1, account=Account{money=1000}} |
从结果上可以看出:当修改原始的嵌套对象Account
的属性值时,目标对象的Account对象的值也跟着变化。
很显然,这与我们预想的对象属性拷贝是相违背的。面对这种情况,我们可以把对象Account单独拉出来,进行依次属性值拷贝,然后再进行封装。
1 | public static void main(String[] args) { |
这样即使Account
对象数据发生变化,也不会改变目标对象的数据,现在的情况是User只有一个嵌套对象,如果有很多个呢?这种方式就不可取了,那么就要用到深拷贝。
2.2 深拷贝
2.2.1 通过写入文件方式
Java深拷贝有两种实现方式, 第一种是通过将对象序列化到临时文件,然后再通过反序列化方式,从临时文件中读取数据,具体操作如下:
首先所有的类,必须要实现Serializable
接口,推荐显示定义序列化ID
1 | public class User implements Serializable { |
对象序列化与反序列化:
1 | public static void main(String[] args) { |
输出结果:
1 | 修改嵌套对象属性前的结果: User{userId=1, account=Account{money=1000}} |
通过序列化和反序列化的方式,可以实现多层复杂度的对象数据拷贝。
因为涉及到需要将数据写入临时磁盘,性能可能会有所下降。
2.2.2 JSON序列化和反序列化
采用json序列化和反序列化的技术实现,同时性能也比将数据写入临时磁盘的方式要好很多。并且不需要显示实现序列化接口。
json序列化和反序列化的底层思想是:将对象序列化成字符串;然后再将字符串通过反序列化方式成对象。
- 引入相关的
jackson
包1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
- 编写统一Json处理工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90/**
* @author xiaoyuge
*/
public class JsonUtils {
private static final Logger log = LoggerFactory.getLogger(JsonUtils.class);
private static ObjectMapper objectMapper = new ObjectMapper();
static {
// 序列化时,将对象的所有字段全部列入
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
// 允许没有引号的字段名
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 自动给字段名加上引号
objectMapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, true);
// 时间默认以时间戳格式写,默认时间戳
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
// 忽略空bean转json的错误
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// 设置时间转换所使用的默认时区
objectMapper.setTimeZone(TimeZone.getDefault());
// 反序列化时,忽略在json字符串中存在, 但在java对象中不存在对应属性的情况, 防止错误
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
//序列化/反序列化,自定义设置
SimpleModule module = new SimpleModule();
// 序列化成json时,将所有的long变成string
module.addSerializer(Long.class, ToStringSerializer.instance);
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
// 自定义参数配置注册
objectMapper.registerModule(module);
}
/**
* 对象序列化成字符串
*
* @param obj 对西那个
* @param <T> 字符串
* @return 序列化
*/
public static <T> String objToStr(T obj) {
if (null == obj) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
log.warn("objToStr error: ", e);
return null;
}
}
/**
* 字符串反序列化成对象
*
* @param str
* @param clazz
* @param <T>
* @return
*/
public static <T> T strToObj(String str, Class<T> clazz) {
try {
return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
} catch (Exception e) {
log.warn("strToObj error: ", e);
return null;
}
}
/**
* 字符串反序列化成对象(数组)
*
* @param str
* @param typeReference
* @param <T>
* @return
*/
public static <T> T strToObj(String str, TypeReference<T> typeReference) {
try {
return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
} catch (Exception e) {
log.warn("strToObj error", e);
return null;
}
}
} - 编写测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14public static void main(String[] args) {
Account sourceAccount = new Account();
sourceAccount.setMoney(BigDecimal.valueOf(1000));
User sourceUser = new User();
sourceUser.setUserId(1L);
sourceUser.setAccount(sourceAccount);
//json 序列化和反序列化
User targetUser = JsonUtils.strToObj(JsonUtils.objToStr(sourceUser), User.class);
System.out.println("修改嵌套对象属性前的结果: "+targetUser.toString());
sourceAccount.setMoney(BigDecimal.valueOf(2000));
System.out.println("修改嵌套对象属性后的结果: "+targetUser.toString());
}
- 查看输出结果
1
2修改嵌套对象属性前的结果: User{userId=1, account=Account{money=1000}}
修改嵌套对象属性后的结果: User{userId=1, account=Account{money=1000}}
3. 总结
- 浅拷贝下,原对象和目标对象,引用都是同一个对象,当被引用的对象数据发生变时,相关的引用者也会跟着一起改变
- 深拷贝下,原对象和目标对象数据时两个完全独立的存在,相互之间不受影响
- 如果对象需要深拷贝,推荐采用json序列化和反序列化的方式实现,相比通过文件写入的方式进行序列化和反序列化,操作简单且性能高。