目录
1.spring里的beanUtils:copyProperties(Object source, Object target)方法,实现的是浅拷贝
概念解释
一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。
深、浅拷贝是用来去描述对象或者对象数组中引用数据类型的一个复制场景的。
1.浅拷贝只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象。就是只复制某一个对象的指针,而不复制这个对象本身。这种复制方式,意味着两个引用指针指向被复制对象的同一块内存地址。浅拷贝出来的对象,内部的类属性指向的是同一个对象。
2.深拷贝既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,复制一个完全一模一样的对象,新对象和老对象之间不共享任何内存。也就意味着对新对象的修改,不影响老对象的值。深拷贝出来的对象,内部的类执行指向的不是同一个对象。
通过序列化的方式来实现深拷贝:先将一个序列化一遍,再反序列化回来,就会得到一个完整的新对象。
如果使用错了拷贝方法,会造成多个线程同时操作同一个对象,造成数据安全问题。
具体实现
1.浅拷贝的实现:
Java已经内置了 Cloneable 抽象原型接口,自定义的类型只需实现该接口并重写 Object.clone() 方法即可完成本类的浅拷贝(对于对象字段只拷贝引用指针)。
Cloneable 是一个空接口。Java之所以提供 Cloneable 接口,只是为了在运行时通知Java虚拟机可以安全地在该类上使用【不安全是体现在哪里?】 clone() 方法。而如果该类没有实现 Cloneable 接口,则调用 clone() 方法会抛出 CloneNotSupportedException 异常。
一般情况下,如果使用 clone() 方法,则需满足以下条件:
1、对任何对象 o,都有 o.clone() != o。换言之,克隆对象与原型对象不是同一个对象;
2、对任何对象 o,都有 o.clone().getClass() == o.getClass()。换言之,复制对象与原对象的类型一样;
3、如果对象 o 的 equals() 方法定义恰当,则 o.clone().equals(o) 应当成立。
public class User {
private String name;
private String psw;
}
public class test {
public static void main(String[] args) {
User user1 = new User();
user1.setName("柯意");
//实现浅拷贝
User user2 =user1;
System.out.println("user1:"+user1);
System.out.println("user2:"+user2);
System.out.println("user1的name:"+user1.getName());
System.out.println("user2的name:"+user2.getName());
user2.setName("大帅哥");
System.out.println("user1的name:"+user1.getName());
System.out.println("user2的name:"+user2.getName());
}
}
//运行结果:
//user1:com.rwws.system.keyi.User@1ddc4ec2---
//user2:com.rwws.system.keyi.User@1ddc4ec2--->两个对象的引用地址一样
//user1的name:柯意
//user2的name:柯意
//user1的name:大帅哥
//user2的name:大帅哥
2.深拷贝的实现
常见的几种深拷贝方式(这6种都是分配了新的内存空间):
1.构造函数方式
首先在bean对象中添加有参的构造函数(形参如果是基本类型和字符串则是直接赋值,如果是对象,则是重新new一个),在创建对象时,通过new的方式完成;new的方式可以让每一个对象都是新创建的,它们之间互不干扰,但是new的方式在对象个数少的情况下,勉强能够使用,在创建对象过多时,对系统的开销很大,所以不推荐通过这种方式完成。
弊端:一个对象形参很多的时候就很麻烦了
public class User {
private String name;
private String psw;
//无参构造器
public User() {
}
//增加有参构造器
public User(String name, String psw) {
this.name = name;
this.psw = psw;
}
}
public class test02 {
public static void main(String[] args) {
User user1 = new User("柯意", "123456");
User user2 = new User("柯意", "123456");
System.out.println("user1:"+user1);
System.out.println("user2:"+user2);
}
}
//打印结果:
//user1:com.rwws.system.keyi.User@1ddc4ec2--->两者的地址不一样
//user2:com.rwws.system.keyi.User@133314b
2.重写clone方法
(1).重写Object父类继承而来的clone()方法,并修改为public
ctrl+o调出重写方法,选择clone():Object
默认生成的代码为:
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
根据需求改写之后:
//clone默认是protected的,要改成public,默认返回object 把返回类型改为bean对象
@Override
public User clone() throws CloneNotSupportedException {
return(User)super.clone();
}
(2).实现Cloneable接口来告诉jvm此类允许拷贝
public class User implements Cloneable
public class test03 {
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User("柯意", "123456");
User user2 = user1.clone();
System.out.println("user1:"+user1);
System.out.println("user2:"+user2);
System.out.println("user1的name:"+user1.getName());
System.out.println("user2的name:"+user2.getName());
user2.setName("大帅哥");
System.out.println("user1的name:"+user1.getName());
System.out.println("user2的name:"+user2.getName());
}
}
//打印结果:
//user1:com.rwws.system.keyi.User@1ddc4ec2--->两者的地址不一样
//user2:com.rwws.system.keyi.User@133314b
//user1的name:柯意
//user2的name:柯意
//user1的name:柯意
//user2的name:大帅哥
注意:像上面那样重写clone(),super.clone()其实是浅拷贝,只对String等类型的字段有效,对是类对象的字段仍是浅拷贝状态。也就是clone这个方法的弊端是如果所有字段都是对象的话,完全无法实现深拷贝。所以在重写User类的clone()方法时,存在对象字段需要调用对应的对象类的clone()重新赋值。
例如:对user类新增一个region对象字段
public class User implements Cloneable {
private String name;
private String psw;
private region reg;
}
public class region {
private String province;
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public region() {
}
public region(String province, String city) {
this.province = province;
this.city = city;
}
}
public class test {
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User("柯意", "123456");
user1.setReg(new region("福建","福州"));
User user2 = user1.clone();
System.out.println("user1:"+user1);
System.out.println("user2:"+user2);
System.out.println("user1的name:"+user1.getName());
System.out.println("user2的name:"+user2.getName());
//两个实例中的对象字段
System.out.println("user1的region:"+user1.getReg());
System.out.println("user2的region:"+user2.getReg());
}
}
//输出内容:
//user1:com.rwws.system.keyi.User@1ddc4ec2--->
//user2:com.rwws.system.keyi.User@133314b--->两个user不是同一个对象(地址
//user1的name:柯意
//user2的name:柯意
//user1的region:com.rwws.system.keyi.region@b1bc7ed--->但是两个user里的region对象是同一个对象(地址)
//user2的region:com.rwws.system.keyi.region@b1bc7ed
所以,重写clone方法,但是只是super.clone()的话,不是完全实现深拷贝,还需要另外单独对对象字段(例如上面的region类进行clone()重写)。
----->重写之后
//region自己先实现cloneable接口(目前这个类里没有引用数据类型也就是没有对象字段,直接super.clone;即可)
@Override
public region clone() throws CloneNotSupportedException {
return (region) super.clone();
}
//在user中重写user的clone方法↓
//需要注意的是,super.clone()其实是浅拷贝
//所以在重写User类的clone()方法时,region对象需要调用region.clone()重新赋值
@Override
public User clone() throws CloneNotSupportedException {
User user = (User)super.clone();
user.setReg(this.reg.clone());
return user;
}
//接着测试
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User("柯意", "123456");
user1.setReg(new region("福建","福州"));
User user2 = user1.clone();
System.out.println("user1:"+user1);
System.out.println("user2:"+user2);
//两个实例中的对象字段
System.out.println("user1的region:"+user1.getReg());
System.out.println("user2的region:"+user2.getReg());
}
//得到输出的内容:
//user1:com.rwws.system.keyi.User@1ddc4ec2
//user2:com.rwws.system.keyi.User@133314b
//user1的region:com.rwws.system.keyi.region@b1bc7ed
//user2的region:com.rwws.system.keyi.region@7cd84586
//可以看出两个user的地址不同,内部的region也不同
3.Apache Commons Lang序列化
Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。
Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。
4.Gson序列化
5.Jackson序列化