Spring的BeanUtils和Apache的BeanUtils这两种工具实质上就是对象拷贝工具,而对象拷贝又分为深拷贝和浅拷贝,下面进行详细解释。
前言
在Java中,除了 基本数据类型之外,还存在类的实例对象这个引用数据类型,而一般使用 “=” 号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际还是指向的同一个对象。
而浅拷贝和深拷贝就是在这个基础上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。
反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
简单来说:
- 浅拷贝:两个对象拷贝时,对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
- 深拷贝:两个对象拷贝时,对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
BeanUtils
Apache 的 BeanUtils
BeanUtils的例子
1 |
|
1 | public class TestApacheBeanUtils { |
从上面的例子可以看出,对象拷贝非常简单,BeanUtils最常用的方法就是:
1 | // 将源对象中的值拷贝到目标对象 |
默认情况下,使用org.apache.commons.beanutils.BeanUtils
对复杂对象的复制是引用,这是一种浅拷贝
但是由于 Apache下的BeanUtils对象拷贝性能太差,不建议使用,而且在阿里巴巴Java开发规约插件上也明确指出:
Ali-Check | 避免用Apache Beanutils进行属性的copy。
commons-beantutils 对于对象拷贝加了很多的检验,包括类型的转换,反射,甚至还会检验对象所属的类的可访问性,可谓相当复杂,这也造就了它的差劲的性能,具体实现代码如下:
1 | public void copyProperties(final Object dest, final Object orig) |
Spring的 BeanUtils
使用Spring的BeanUtils进行对象拷贝:
1 | public class TestSpringBeanUtils { |
Spring下的BeanUtils也是使用 copyProperties
方法进行拷贝,只不过它的实现方式非常简单,就是对两个对象中相同名字的属性进行简单的get/set,仅检查属性的可访问性。
去源码里面:
- 版本:spring-beans-4.3.8.RELEASE
- 方法:
org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object, java.lang.Class<?>, java.lang.String...)
1 | private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException { |
这个方法的源码其实很短,只有 44 行,我给大家把关键的地方写上注释,截图如下:
可以看到,成员变量赋值是基于目标对象的成员列表,并且会跳过ignore的以及在源对象中不存在,所以这个方法是安全的,不会因为两个对象之间的结构差异导致错误,但是必须保证同名的两个成员变量类型相同。
所以从上面的源码解读中我可以得到这样的几条结论:
- 对于要复制的属性,目标对象必须要有对应的 set 方法(上图的第 27 行),源对象必须要有对应的 get 方法(上图的第 34 行)。
- 对于要复制的属性,目标对象和源对象的属性名称得一模一样。(上图的第 34 行)。
- 目标对象对应的 set 方法的入参和源对象对应的 get 方法的返回类型必须得一致(上图的第 37 行)。
进行对象之间的属性赋值
对象之间属性的赋值
使用org.springframework.beans.BeanUtils.copyProperties
方法进行对象之间属性的赋值,避免通过get
、set
方法一个一个属性的赋值
1 | /** |
List集合之间的对象属性赋值
1 | /** |
比如有两个类,User和Employee,将存储Employee对象的List赋给存储User对象的List。
User类:
1 | public class User { |
Employee类:
1 | public class Employee { |
测试:
1 |
|
深拷贝,浅拷贝
这里说的是Spring的BeanUtils.copyProperties
。
场景
开发中经常遇到,把父类的属性拷贝到子类中。通常有2种方法:
- 一个一个set
- 用
BeanUtils.copyProperties
很显然BeanUtils
更加方便,也美观很多。
那么任何情况都能使用BeanUtils
么,当然不是,要先了解他。
BeanUtils是深拷贝,还是浅拷贝?
是浅拷贝。
- 浅拷贝:只是调用子对象的set方法,并没有将所有属性拷贝。(也就是说,引用的一个内存地址)
- 深拷贝:将子对象的属性也拷贝过去。
什么情况适合用BeanUtils
如果都是单一的属性,那么不涉及到深拷贝的问题,适合用BeanUtils。
有子对象就一定不能用BeanUtils么
并不绝对,这个要区分考虑:
- 子对象还要改动。
- 子对象不怎么改动。
虽然有子对象,但是子对象并不怎么改动,那么用BeanUtils也是没问题的。
代码例子
下面用代码说明下。
- 翠山有个儿子无忌,儿子继承了他的face和height。
- 但是life应该是自己的。
- 后来翠山自刎而死,无忌也变成dead状态了,这就是浅拷贝,无忌用的life引用的翠山的life对象。
Father类:
1 |
|
Life 类:
1 |
|
Son类和main方法:
1 |
|
上面注释出的代码可以如下替换:
case1和case2还是受浅拷贝的影响,case3不受。
case1:翠山自刎,无忌也挂了
1 | // Life wujiLife = wuji.getLife(); |
case2:翠山自刎,无忌设置或者,翠山也活了
1 | // cuishanLife.setStatus("dead"); // 翠山后来自刎了 |
case3:翠山和无忌互不影响
1 | cuishanLife.setStatus("dead"); // 翠山自刎了,该行放在上下均可 |
dest,src 还是 src,dest
常见的BeanUtils有2个:
Spring有BeanUtils
apache的commons也有BeanUtils
区别如下:
这2个用哪个都行,但是要注意区别。
因为他们2个的src和dest是正好相反的,要特别留意。
小结
以上简要的分析两种BeanUtils,因为Apache下的BeanUtils性能较差,不建议使用,可以使用 Spring的BeanUtils,或者使用其他拷贝框架,比如 cglib BeanCopier,基于javassist的Orika等,这些也是非常优秀的类库,值得去尝试。