拷贝
对象拷贝
- 将一个对象的值赋值给另一个对象的现象。
- 对于对象拷贝分为深拷贝和浅拷贝
public class Subject {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "[Subject: " + "哈希值:" + this.hashCode() + ",name:" + name + "]";
}
}
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "[Student: " + "哈希值:" + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
public class Test {
public static void main(String[] args) {
Subject subject = new Subject("语文");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("张三");
studentA.setAge(20);
// 对象拷贝后没有生成新的对象,二者的对象地址是一样的
Student studentB = studentA;
studentB.setName("李四");
studentB.setAge(18);
Subject subjectB = studentB.getSubject();
subjectB.setName("数学");
System.out.println("studentA:" + studentA.toString());
System.out.println("studentB:" + studentB.toString());
}
}
浅拷贝
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
- 如果属性是基本类型,拷贝的就是基本类型的值;
- 如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + "哈希值:" + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
public static void main(String[] args) {
Subject subject = new Subject("语文");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("张三");
studentA.setAge(20);
// 通过 studentA.clone() 拷贝对象后得到的 studentB,和 studentA 是两个不同的对象。
// studentA 和 studentB 的基础数据类型的修改互不影响,而引用类型 subject 修改后是会有影响的
Student studentB = (Student)studentA.clone();
studentB.setName("李四");
studentB.setAge(18);
Subject subjectB = studentB.getSubject();
subjectB.setName("数学");
System.out.println("studentA:" + studentA.toString());
System.out.println("studentB:" + studentB.toString());
}
深拷贝
- 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)
- 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
- 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝
public class Subject {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
return super.clone();
}
@Override
public String toString() {
return "[Subject: " + "哈希值:" + this.hashCode() + ",name:" + name + "]";
}
}
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//深拷贝
try {
// 改为深复制:
Student student = (Student) super.clone();
// 本来是浅复制,现在将Subject对象复制一份并重新set进来
student.setSubject((Subject) student.getSubject().clone());
return student;
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + "哈希值:" + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
public class Test {
public static void main(String[] args) {
Subject subject = new Subject("语文");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("张三");
studentA.setAge(20);
// 通过 studentA.clone() 拷贝对象后得到的 studentB,和 studentA 是两个不同的对象。
// studentA 和 studentB 的基础数据类型的修改互不影响,而引用类型 subject 修改后是会有影响的
Student studentB = (Student)studentA.clone();
studentB.setName("李四");
studentB.setAge(18);
Subject subjectB = studentB.getSubject();
subjectB.setName("数学");
System.out.println("studentA:" + studentA.toString());
System.out.println("studentB:" + studentB.toString());
}
}
Apache BeanUtils
- 对象的拷贝:BeanUtils.copyProperties(Object dest,Object orig)
- 对象属性的拷贝:BeanUtils.copyProperties(Object bean,String name,Object value)或者BeanUtils.setProperty(Object bean,String name,Object value)
- map数据封装到Javabean(要map中的数据封装到JavaBean中去,需要map中的key与JavaBean里面的私有化的属性要相匹配):populate(Object bean, Map<String,? extends Object> properties)
避免用Apache Beanutils进行属性的copy?
- 默认情况下,使用org.apache.commons.beanutils.BeanUtils对复杂对象是进行的浅拷贝,但是由于 Apache下的BeanUtils对象拷贝性能太差,不建议使用,这在阿里巴巴Java开发规约插件上也明确指出。
- 因为在对对象进行拷贝的时候添加很多的校验,比如像类型转换,甚至还校验了对象所属类的访问权限,可以说校验是相当的复杂,这也就是造成性能差的根本原因。
Spring BeanUtils
<!-- Spring beans包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.19.RELEASE</version>
</dependency>
- Spring下的BeanUtils也是使用 copyProperties方法进行拷贝
Student user1 = new Student(1L,"张三111","123456");
Student user2 = new Student(2L,"张三222","123456");
BeanUtils.copyProperties(user1,user2);
System.out.println(user1);
System.out.println(user2);
如果user1 和user2 间存在名称不相同的属性,则BeanUtils不对这些属性进行处理,需要手动处理。