1、两个包下的BeanUtils.copyProperties对比

BeanUtils是开发中常用到的工具类,而获取这一工具类主要是通过导入org.springframework.beans.BeanUtils或者org.apache.commons.beanutils.BeanUtils包来获取,但是不同的包中BeanUtils的方法使用是不一样的,接下来就对这两个包中的copyProperties方法进行对比。

先来看一下这两个包中的copyProperties方法的定义:

//org.springframework.beans.BeanUtils
public static void copyProperties(Object source, Object target){....}

//org.apache.commons.beanutils.BeanUtils
public static void copyProperties(Object dest,Object orig){....}

由定义可知,在org.springframework.beans.BeanUtils包下的copyProperties第一个参数是被copy的对象,而org.apache.commons.beanutils.BeanUtils中是第二个参数,所以使用时不要弄混。

1)接下来定义两个实体类Student和Teacher,用来模拟目标对象(target/dest)中包含被copy的对象(source/orig)的所有字段时

@Data
public class Student {
	private String id;
	private String name;
	private String age;
    
    public Student(String id, String name, String age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
}

@Data
public class Teacher {
	private String id;
	private String name;
	private String age;
	private String sex;   //多出来的参数

    public Teacher() {}
	public Teacher(String id, String name, String age, String sex) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.sex = sex;
	}	
}

先使用org.springframework.beans.BeanUtils下的copyProperties来进行测试,执行代码如下:

import org.springframework.beans.BeanUtils;

public static void main(String[] args) throws Exception {
	Student student = new Student(UUID.randomUUID().toString(), "zhangsan", "19");
	Teacher teacher = new Teacher();
	System.out.println(student.toString());
	BeanUtils.copyProperties(student,teacher);
	System.out.println(teacher.toString());
}
执行结果如下:
	Student(id=00c47e77-785d-4939-89db-e757979050ec, name=zhangsan, age=19)
	Teacher(id=00c47e77-785d-4939-89db-e757979050ec, name=zhangsan, age=19, sex=null)

当将引入的包改为org.apache.commons.beanutils.BeanUtils时,将上述中的执行代码进行如下修改:

BeanUtils.copyProperties(teacher,student); //因为apache包下被copy的参数在第二个位置
执行结果如下:
	Student(id=900d0b93-1913-4022-b86b-127682cd9f5c, name=zhangsan, age=19)
	Teacher(id=00c47e77-785d-4939-89db-e757979050ec, name=zhangsan, age=19, sex=null)

小结:当目标对象(target/dest)中包含被copy的对象(source/orig)的所有字段时,两种包下的copyProperties方法均可以执行成功。

2)对上述中说到的实体类进行修改,即目标对象(target/dest)中不包含被copy的对象(source/orig)的所有字段时,修改后的内容如下:

@Data
public class Student {
	private String id;
	private String name;
	private String age;
    
    public Student(String id, String name, String age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
}

@Data
public class Teacher {
	private String id;
	private String name;
    
	public Teacher() {}
	public Teacher(String id, String name) {
		this.id = id;
		this.name = name;
	}	
}

执行代码与1)中的一样这里就不写了,先使用org.apache.commons.beanutils.BeanUtils中的copyProperties方法,执行结果如下:

    Student(id=6f6711ef-fa2f-420d-b0b3-b76998356533, name=zhangsan, age=19)

    Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/collections/FastHashMap
        at org.apache.commons.beanutils.PropertyUtilsBean.getPropertyDescriptor(PropertyUtilsBean.java:964)
        at org.apache.commons.beanutils.PropertyUtilsBean.isWriteable(PropertyUtilsBean.java:1479)
        at org.apache.commons.beanutils.BeanUtilsBean.copyProperties(BeanUtilsBean.java:280)
        at org.apache.commons.beanutils.BeanUtils.copyProperties(BeanUtils.java:135)
        at com.learn.controller.BeanController.main(BeanController.java:39)
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.collections.FastHashMap
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 5 more

接下来使用org.springframework.beans.BeanUtils下的copyProperties方法,执行结果如下:

    Student(id=87665c46-9a6f-4936-9c9d-1513e29f2bca, name=zhangsan, age=19)
    Teacher(id=87665c46-9a6f-4936-9c9d-1513e29f2bca, name=zhangsan)

小结:目标对象(target/dest)中不包含被copy的对象(source/orig)的所有字段时,应选用org.springframework.beans.BeanUtils下的copyProperties方法

3)基于2)中的实体类来看下另外一种情况,修改下执行代码,实体类不变:

	public static void main(String[] args) throws Exception {
		Student student = new Student(UUID.randomUUID().toString(), "zhangsan", "19");
		Teacher teacher = new Teacher("12334","lisi");
		System.out.println(student.toString());
		BeanUtils.copyProperties(student,teacher);
		System.out.println(teacher.toString());
	}

先使用org.apache.commons.beanutils.BeanUtils中的copyProperties方法,执行结果如下:

Student(id=16bf375a-5925-4411-8bdf-158b3373d261, name=zhangsan, age=19)
Teacher(id=12334, name=zhangsan)
Student(id=12334, name=zhangsan, age=19)

接下来使用org.springframework.beans.BeanUtils下的copyProperties方法,执行结果如下:

Student(id=2df2fba9-22a2-4cb6-86d7-3d5b47475114, name=zhangsan, age=19)
Teacher(id=12334, name=zhangsan)
Student(id=12334, name=zhangsan, age=19)

小结:目标对象(target/dest)中包含被copy的对象(source/orig)的所有字段时,两个包下的copy方法都可以,而且目标对象(target/dest)中多于的对象的值不会被覆盖掉。

总结:

​ 1、org.apache.commons.beanutils.BeanUtils和org.springframework.beans.BeanUtils两个包中的copyProperties方法目标对象和源对象参数的位置是相反,使用时需要注意。

​ 2、使用org.apache.commons.beanutils.BeanUtils进行copy对象时,被copy的对象(source/orig)中包含的字段目标对象(target/dest)必须包含,可以有其他的多于字段,类型可以不相同,但字段名称必须一致;org.springframework.beans.BeanUtils中的没有这个限制。

2、BeanUtils.copyProperties的深浅拷贝问题
2.1、浅拷贝和深拷贝

先来说一下什么是浅拷贝,深拷贝;两者最主要的区别就在于是否是复制了对象的真实实体还是说只是使用了一个指针,两者指向的是内存中的同一个对象。

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址;

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存;同时如果一个类中包含有引用类型变量,则该类拷贝以后引用类型也会被拷贝。

参考博文:
1、深拷贝和浅拷贝的区别
2、java中clone方法的理解(深拷贝、浅拷贝)
3、System.arraycopy()使用原理解析,坑点之:深拷贝和浅拷贝

2.2、BeanUtils.copyProperties深浅拷贝问题

BeanUtils.copyProperties使用起来虽然方便,但是它其实只是浅拷贝,所以类中都是单一的属性,像咱们上边介绍的,可以直接用;如果类中包含有其他的子类就需要考虑下拷贝后目标对象(target/dest)中包含被copy的对象(source/orig)中的子类是否有可能被修改,如果有可能被修改就不能直接使用。

新增加一个实体类Life,接下来我们将上述中的代码进行一个改造:

@Data
public class Life {
	private String life;

	public Life(String life) {
		this.life = life;
	}
}
@Data
public class Teacher {
	private String id;
	private String name;
	private Life life;

	public Teacher(String id, String name) {
		this.id = id;
		this.name = name;
	}
	public Teacher() {}
}

@Data
public class Student {
	private String id;
	private String name;
	private String age;
	private Life life;
    
    public Student(String id, String name, String age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
}
import org.springframework.beans.BeanUtils;
public class BeanController {

	public static void main(String[] args) throws Exception {
		Student student = new Student(UUID.randomUUID().toString(), "zhangsan", "19");
		Life life=new Life("young");
		student.setLife(life);
		Teacher teacher = new Teacher();
		System.out.println(student.toString());
		BeanUtils.copyProperties(student,teacher);
		teacher.getLife().setLife("old");
		System.out.println(student.toString());
	}
}
执行结果如下:
	Student(id=988cac88-638c-4e52-8421-db39590ed32c, name=zhangsan, age=19, life=Life(life=young))
Student(id=988cac88-638c-4e52-8421-db39590ed32c, name=zhangsan, age=19, life=Life(life=old))

正常情况下,Student中的life值应该为young,但是student拷贝给teacher以后,随着teacher中life修改,student中的life也被修改,说明teacher和student两个对象中的life子对象为内存中的同一个life对象,所以BeanUtils.copyProperties属于浅拷贝。

参考博文:BeanUtils.copyProperties深拷贝的使用

3、深拷贝的方法

这里介绍一种深拷贝的方法,先将集合转化为字节数组输出流,然后在读取,这样就可以实现深度拷贝,代码如下:

	public <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {  
		// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
	    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();  
	    ObjectOutputStream out = new ObjectOutputStream(byteOut);  
	    out.writeObject(src);  
	  
	    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());  
	    ObjectInputStream in = new ObjectInputStream(byteIn);  
	    @SuppressWarnings("unchecked")  
	    List<T> dest = (List<T>) in.readObject();  
	    return dest;  
	}  

参考博文:序列化(Serialization)实现深拷贝

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐