在java语言中,如果需要拷贝一个对象时,分两种类型的拷贝:浅拷贝和深拷贝
在拷贝的同时又不想改变原有对象的属性值 ,就只能是深拷贝
两者的区别如下:
浅拷贝:只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。
深拷贝:只是拷贝了源对象的值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。
浅拷贝示例如下:
- 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,在设置新值的时候,改变了原有被拷贝对象的值,这就是典型的浅拷贝
深拷贝经过总结,常见的有6种实现方法

通过在调用构造函数进行深拷贝,形参如果是基本类型和字符串则直接赋值,如果是对象则重新new一个。
- Big big = new Big("1", new Address("path1"));
- Big big2 = new Big("2", new Address("path2"));
优缺点上图所示,比较常见的用法,但是局限性很大
这个地方需要注意三个点
- 需要实现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;
- }
- }
-
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;
- }
- }
Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。
需要注意两个点:
- 引入Google第三方Jar包
生成方式: Gson gson = new Gson(); Big big2 = gson.fromJson(gson.toJson(big), Big.class);
com.google.code.gson gson 2.9.0
- 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;
- }
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("常温");
- }
在对象类中自定义方法,实现序列化和反序列化
需要注意的是
- 实现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方式,毕竟都是常用的工具类,代码契合度高