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序列化和反序列化的方式实现,相比通过文件写入的方式进行序列化和反序列化,操作简单且性能高。
 
         
              