1.序言
在java语言中,如果需要拷贝一个对象时,分两种类型的拷贝:浅拷贝和深拷贝
在拷贝的同时又不想改变原有对象的属性值 ,就只能是深拷贝
两者的区别如下:
-
浅拷贝:只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。
-
深拷贝:只是拷贝了源对象的值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。
2.浅拷贝
浅拷贝示例如下:
public static void main(String[] args) {
Big big = new Big("1", new Address("path1"));
Big big3 = big;
big3.setStart("3");
big3.getAddress().setPath("path3");
System.out.println(big);
System.out.println(big3);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Big {
private String start;
private Address address;
}
执行结构如下:
Big(start=3, address=Address(path=path3))
Big(start=3, address=Address(path=path3))
可以清晰的发现拷贝对象big3,在设置新值的时候,改变了原有被拷贝对象的值,这就是典型的浅拷贝
3.深拷贝
深拷贝经过总结,常见的有6种实现方法
- 构造方法
- 重写clone方法
- Apache Commonss Lang序列化
- Gson序列化
- Jackson序列化
- 自定义序列化
3.1.构造方法
通过在调用构造函数进行深拷贝,形参如果是基本类型和字符串则直接赋值,如果是对象则重新new一个。
Big big = new Big("1", new Address("path1"));
Big big2 = new Big("2", new Address("path2"));
优缺点上图所示,比较常见的用法,但是局限性很大
3.2.实现Cloneable接口,重写Clone方法
这个地方需要注意三个点
- 需要实现Cloneable接口,重写Clone方法,包括被拷贝的对象和对象中的引用对象,例如Test和Address
- 引用对象属性需要重新赋值,例如 test.address = address.clone();
- 通过原有对象的clone方法生成新对象,例如 Test test2 = test.clone();
public static void main(String[] args) {
Test test = new Test("1", new Address("path1"));
Test test2 = test.clone();
test2.setStart("2");
test2.getAddress().setPath("path2");
System.out.println(test);
System.out.println(test2);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Test implements Cloneable {
private String start;
private Address address;
@Override
protected Test clone(){
Test test = null;
try {
test = (Test) super.clone();
test.address = address.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return test;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Cloneable{
private String path;
@Override
protected Address clone() {
Address address = null;
try {
address = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}
3.3.Apache序列化
Java提供了序列化的能力,可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类需要实现Serializable接口。Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。
需要注意三个点:
- 实现Serializable接口,包括被拷贝的对象和对象中的引用对象,例如Demo和Address
- 引入Apache第三方Jar包
生成方式:Demo demo2 = SerializationUtils.clone(demo);
public static void main(String[] args) {
Demo demo = new Demo("1", new Address("path1"));
Demo demo2 = SerializationUtils.clone(demo);
demo2.setStart("2");
demo2.getAddress().setPath("path2");
System.out.println(demo);
System.out.println(demo2);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Demo implements Serializable {
private String start;
private Address address;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Serializable{
private String path;
@Override
protected Address clone() {
Address address = null;
try {
address = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}
3.4.Gson序列化
Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。
需要注意两个点:
- 引入Google第三方Jar包
生成方式: Gson gson = new Gson(); Big big2 = gson.fromJson(gson.toJson(big), Big.class);
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.0</version> </dependency>
public static void main(String[] args) {
Big big = new Big("1", new Address("path1"));
Gson gson = new Gson();
Big big2 = gson.fromJson(gson.toJson(big), Big.class);
big2.setStart("2");
big2.getAddress().setPath("path2");
System.out.println(big);
System.out.println(big2);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Big {
private String start;
private Address address;
}
3.5.Jackson序列化
Jackson与Gson相似,可以将对象序列化成JSON。不过拷贝的类需要有默认的无参构造函数。这里不再细说了。
public static void main(String[] args) {
Food food1 =new Food("百事可乐","冰");
//拷贝
ObjectMapper objectMapper = new ObjectMapper();
Food food2 =objectMapper.readValue(objectMapper.writeValueAsString(food1), Food.class));
food2.setType("常温");
}
3.6.自定义序列化
在对象类中自定义方法,实现序列化和反序列化
需要注意的是
- 实现Serializable接口,包括被拷贝的对象和对象中的引用对象
- 需要增加自定义的deepClone方法,包括被拷贝的对象和对象中的引用对象
@Data
@AllArgsConstructor
@NoArgsConstructor
class Demo implements Serializable {
private String start;
private Address address;
public Object deepClone(){
ByteArrayInputStream bis=null;
ObjectInputStream ois=null;
ByteArrayOutputStream bos=null;
ObjectOutputStream oos=null;
try {
//序列化
bos=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis=new ByteArrayInputStream(bos.toByteArray());
ois=new ObjectInputStream(bis);
Person p= (Person) ois.readObject();
return p;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
总结:推荐Apache和Gson方式,毕竟都是常用的工具类,代码契合度高