几种深拷贝方式

利用json转换深拷贝

Posted by John Doe on 2023-01-08
Words 2.7k and Reading Time 13 Minutes
Viewed Times

背景

由于业务代码的方法将传递的引用数据类型参数进行了修改,后续业务又需要修改前的参数,所以需要在修改前进行深拷贝保持修改前的对象。

java值传递

基本数据类型

代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class TestJavaPass {
public static void main(String[] args) {
TestJavaPass tj = new TestJavaPass();
int i=10;
tj.pass(i);
System.out.println("print in main, i is "+i);
}
public void pass(int j){
j=20;
System.out.println("print in pass, j is "+j);
}
}

结果:

1
2
print in pass, j is 20
print in main, i is 10

结论:

传入基本数据类型时,为值传递不会改变其本身

引用数据类型

情况一,传入对象并改变其自身属性

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestJavaPass {
public static void main(String[] args) {
TestJavaPass tj = new TestJavaPass();
// int i=10;
// tj.pass(i);
// System.out.println("print in main, i is "+i);
User user = new User();
user.setName("hollis");
user.setGender("Male");
tj.pass(user);
System.out.println("print in main , user is "+user);
}
public void pass(int j){
j=20;
System.out.println("print in pass, j is "+j);
}
public void pass(User user){
user.setName("hollischuang");
System.out.println("print in pass , user is "+user);
}
}

结果:

1
2
print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='hollischuang', gender='Male'}

结论:

传入为引用类型时,对象的属性改变,注:这种情况属于特殊的值传递,传递的是实际参数的地址的复制。

情况二,传入对象的引用,不改变对象的属性

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestJavaPass {
public static void main(String[] args) {
TestJavaPass tj = new TestJavaPass();
// int i=10;
// tj.pass(i);
// System.out.println("print in main, i is "+i);
User user = new User();
user.setName("hollis");
user.setGender("Male");
tj.pass(user);
System.out.println("print in main , user is "+user);
}
public void pass(int j){
j=20;
System.out.println("print in pass, j is "+j);
}
public void pass(User user){
user = new User();
user.setName("hollischuang");
System.out.println("print in pass , user is "+user);
}
}

结果:

1
2
print in pass , user is User{name='hollischuang', gender='null'}
print in main , user is User{name='hollis', gender='Male'}

结论:

方法pass中形参user首先指向main方法中的user,然后再新建user类的时候会重新指向新建的user,后续改变新建user,main方法中的user并未改变,这里是值传递,传递的是对象的引用。

java是值传递,基本数据类型传递复制的对象,引用数据类型传递其引用,如果在方法中提供了修改引用对象的方法则会修改其对象,否则不会修改

几种深拷贝方式

1.构造函数深拷贝

我们可以调用构造函数进行深拷贝,形参如果是基本类型和字符串则是直接赋值,如果是对象,则是重新new一个。

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
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
/**
* 通过构造器进行深拷贝测试
*/
@Getter
public class UserConstruct {
private String userName;
private AddressConstruct address;
public UserConstruct() {
}
public UserConstruct(String userName, AddressConstruct address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) {
AddressConstruct address = new AddressConstruct("小区1", "小区2");
UserConstruct user = new UserConstruct("小李", address);
// 调用构造函数进行深拷贝
UserConstruct copyUser = new UserConstruct(user.getUserName(), new AddressConstruct(address.getAddress1(), address.getAddress2()));
// 修改源对象的值
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1() == copyUser.getAddress().getAddress1());
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
// true
System.out.println(user.getAddress().getAddress2().equals(copyUser.getAddress().getAddress2()));
}
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;


@Getter
@Setter
public class AddressConstruct {
private String address1;
private String address2;
public AddressConstruct() {
}
public AddressConstruct(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}

2.重载Clone()方法深拷贝

Object父类有个clone()的拷贝方法,不过它是protected类型的 ,我们需要重写它并修改为public类型,除此之外,子类还需要实现Cloneable接口来告诉JVM这个类上是可以拷贝的。

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
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
*
*/
@Setter
@Getter
public class AddressClone implements Cloneable{
private String address1;
private String address2;
public AddressClone() {
}
public AddressClone(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
@Override
protected AddressClone clone() throws CloneNotSupportedException {
return (AddressClone) super.clone();
}
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
/**
* 通过实现Clone接口实现深拷贝
*/
@Setter
@Getter
public class UserClone implements Cloneable{
private String userName;
private AddressClone address;
public UserClone() {
}
public UserClone(String userName, AddressClone address) {
this.userName = userName;
this.address = address;
}
/**
* Object父类有个clone()的拷贝方法,不过它是protected类型的,
* 我们需要重写它并修改为public类型。除此之外,
* 子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。
* @return
* @throws CloneNotSupportedException
*/
@Override
protected UserClone clone() throws CloneNotSupportedException {
// 需要注意的是,super.clone()其实是浅拷贝,
// 所以在重写UserClone类的clone()方法时,address对象需要调用address.clone()重新赋值
UserClone userClone = (UserClone) super.clone();
userClone.setAddress(this.address.clone());
return userClone;
}
public static void main(String[] args) throws CloneNotSupportedException {
AddressClone address = new AddressClone("小区1", "小区2");
UserClone user = new UserClone("小李", address);
UserClone copyUser = user.clone();
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}

3.Apache Commons Lang序列化方式深拷贝

Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。

Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。

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
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;


@Getter
@Setter
public class AddressSerializable implements Serializable {
private String address1;
private String address2;
public AddressSerializable() {
}
public AddressSerializable(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
package com.lyj.demo.pojo.cloneTest;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.SerializationUtils;
import java.io.Serializable;
/**
* 通过Apache Commons Lang 序列化方式深拷贝
* Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。
* 但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。
* Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。
*/
@Getter
@Setter
public class UserSerializable implements Serializable {
private String userName;
private AddressSerializable address;
public UserSerializable() {
}
public UserSerializable(String userName, AddressSerializable address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) {
AddressSerializable address = new AddressSerializable("小区1", "小区2");
UserSerializable user = new UserSerializable("小李", address);
UserSerializable copyUser = SerializationUtils.clone(user);
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}

4. 将对象序列化成JSON,再将JSON反序列化成对象

4.1 Gson深拷贝
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
package com.lyj.demo.pojo.cloneTest;
import lombok.Data;


@Data
public class AddressGson {
private String address1;
private String address2;
public AddressGson() {
}
public AddressGson(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
package com.lyj.demo.pojo.cloneTest;
import com.google.gson.Gson;
import lombok.Data;
/**
* 使用Gson序列化方式进行深拷贝
* Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝
*/
@Data
public class UserGson {
private String userName;
private AddressGson address;
public UserGson() {
}
public UserGson(String userName, AddressGson address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) {
AddressGson address = new AddressGson("小区1", "小区2");
UserGson user = new UserGson("小李", address);
// 使用Gson序列化进行深拷贝
Gson gson = new Gson();
UserGson copyUser = gson.fromJson(gson.toJson(user), UserGson.class);
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}
4.2 Jackson深拷贝
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
package com.lyj.demo.pojo.cloneTest;
import lombok.Data;


@Data
public class AddressJackson {
private String address1;
private String address2;
public AddressJackson() {
}
public AddressJackson(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
package com.lyj.demo.pojo.cloneTest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
/**
* 通过Jackson方式实现深拷贝
* Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数。
*/
@Data
public class UserJackson {
private String userName;
private AddressJackson address;
public UserJackson() {
}
public UserJackson(String userName, AddressJackson address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) throws JsonProcessingException {
AddressJackson address = new AddressJackson("小区1", "小区2");
UserJackson user = new UserJackson("小李", address);
// 使用Jackson序列化进行深拷贝
ObjectMapper objectMapper = new ObjectMapper();
UserJackson copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), UserJackson.class);
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}
4.3 fastjson深拷贝
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
package com.lyj.demo.pojo.cloneTest;
import lombok.Data;


@Data
public class AddressGson {
private String address1;
private String address2;
public AddressJson() {
}
public AddressJson(String address1, String address2) {
this.address1 = address1;
this.address2 = address2;
}
}
package com.lyj.demo.pojo.cloneTest;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
/**
* 使用fastjson序列化方式进行深拷贝
* fastjson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝
*/
@Data
public class UserJson {
private String userName;
private AddressJson address;
public UserJson() {
}
public UserJson(String userName, AddressJson address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) {
AddressJson address = new AddressJson("小区1", "小区2");
UserJson user = new UserJson("小李", address);
// 使用fastjson序列化进行深拷贝
JSONObject.parseObject(JSONObject.toJSONBytes(condition), UserJson.class);
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}

5.反射实例化新对象

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
91
92
93
94
public static <T> T deepCopy(Object source, Class<T> target) {
if (Objects.isNull(source) || Objects.isNull(target)) {
return null;
}
List<Field> sourceFields = getFields(source.getClass());
List<Field> targetFields = getFields(target);
T t = null;
try {
t = newInstance(source, target, sourceFields, targetFields);
} catch (Exception e) {
e.printStackTrace();
}
return t;
}

/**
* 获取一个类中的所有属性(包括父类属性)
*
* @param c 类名
* @return List<Field>
*/
private static List<Field> getFields(Class<?> c) {
List<Field> fieldList = new ArrayList<>();
Field[] fields = c.getDeclaredFields();
if (fields.length > 0) {
fieldList.addAll(Arrays.asList(fields));
}
return getSuperClassFields(c, fieldList);
}

/**
* 递归获取父类属性
*
* @param o 类名
* @param allFields 外层定义的所有属性集合
* @return 父类所有属性
*/
private static List<Field> getSuperClassFields(Class<?> o, List<Field> allFields) {
Class<?> superclass = o.getSuperclass();
if (Objects.isNull(superclass) || Object.class.getName().equals(superclass.getName())) {
return allFields;
}
Field[] fields = superclass.getDeclaredFields();
if (fields.length == 0) {
return allFields;
}
allFields.addAll(Arrays.asList(fields));
return getSuperClassFields(superclass, allFields);
}

/**
* 目标实例化对象
*
* @param source 原对实例化象
* @param target 目标对象类
* @param sourceFields 源对象字段集合
* @param targetFields 目标对象属性字段集合
* @return 目标实例化对象
*/
private static <T> T newInstance(Object source, Class<T> target, List<Field> sourceFields,
List<Field> targetFields) throws Exception {
T t = target.newInstance();
if (targetFields.isEmpty()) {
return t;
}
for (Field field : sourceFields) {
field.setAccessible(true);
Object o = field.get(source);
Field sameField = getSameField(field, targetFields);
if (Objects.nonNull(sameField)) {
sameField.setAccessible(true);
sameField.set(t, o);
}
}
return t;
}

/**
* 获取目标对象中同源对象属性相同的属性(字段名称,字段类型一致则判定为相同)
*
* @param field 源对象属性
* @param fields 目标对象属性集合
* @return 目标对象相同的属性
*/
private static Field getSameField(Field field, List<Field> fields) {
String name = field.getName();
String type = field.getType().getName();
for (Field f : fields) {
if (name.equals(f.getName()) && type.equals(f.getType().getName())) {
return f;
}
}
return null;
}

额外注意

平常经常使用的org.springframework.beans.BeanUtils.copyProperties(object1,object2)是浅拷贝。


This is copyright.

...

...

00:00
00:00