简述
通过拷贝快速创建相同或相似对象。
接下来我们看一个需要改进的案例。
优化案例
话不多说,先来看一个创建相同或相似对象的传统写法。
最初版v0
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
| public class Department { private String name; private String country; private String province; private String city; private List<Employee> employees;
public String getName() { return name; }
public String getCountry() { return country; }
public String getProvince() { return province; }
public String getCity() { return city; }
public List<Employee> getEmployees() { return employees; }
public Department(String name, String country, String province, String city, List<Employee> employees) { this.name = name; this.country = country; this.province = province; this.city = city; this.employees = employees; } }
public class Employee { private String name; private String sex; private int age; private String country; private String province; private String city; private String post;
public String getName() { return name; }
public String getSex() { return sex; }
public int getAge() { return age; }
public String getCountry() { return country; }
public String getProvince() { return province; }
public String getCity() { return city; }
public String getPost() { return post; }
public Employee(String name, String sex, int age, String country, String province, String city, String post) { this.name = name; this.sex = sex; this.age = age; this.country = country; this.province = province; this.city = city; this.post = post; } }
|
已知一个Department类型的对象,我们想构造一个相似的对象。
1 2 3 4 5 6 7
| public class Client { public static void main(String[] args) { Employee emp = new Employee("张三", "男", 15, "中国", "江西省", "南昌市", "124-1241-1353"); Department department = new Department("开发部", "中国", "江西省", "南昌市", List.of(e)); Department department1 = new Department(department.getName(), department.getCountry(), department.getProvince(), department.getCity(), department.getPost()); } }
|
可以感受到,对象拷贝的朴素写法非常的麻烦。而且想到每一处对象拷贝都需要这样写就感觉头皮发麻。
为了解决这个问题,我们引入原型模式。请看以下样例。
修改版v1(浅拷贝)
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
| public class Department { private String name; private String country; private String province; private String city; private List<Employee> employees;
public Department(String name, String country, String province, String city, List<Employee> employees) { this.name = name; this.country = country; this.province = province; this.city = city; this.employees = employees; } }
public class Employee { private String name; private String sex; private int age; private String country; private String province; private String city; private String post;
public Employee(String name, String sex, int age, String country, String province, String city, String post) { this.name = name; this.sex = sex; this.age = age; this.country = country; this.province = province; this.city = city; this.post = post; } }
|
使用clone()
方法拷贝目标对象。
1 2 3 4 5 6 7 8 9
| public class Client { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("张三", "男", 15, "中国", "江西省", "南昌市", "124-1241-1353"); Department department = new Department("开发部", "中国", "江西省", "南昌市", List.of(e)); Department department1 = (Department)department.clone(); System.out.println(department == department1); System.out.println(department.employees == department1.employees); } }
|
我们发现第8行输出true
,这说明两个对象的employees
的引用相同,这会导致修改其中一个employees
的元素会影响到另一个,这并不好。
如何解决属性相同引用的问题?看以下样例。
修改版v2(深拷贝)
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
| public class Department implements Cloneable { private String name; private String country; private String province; private String city; private List<Employee> employees;
public Department(String name, String country, String province, String city, List<Employee> employees) { this.name = name; this.country = country; this.province = province; this.city = city; this.employees = employees; }
@Override public Object clone() throws CloneNotSupportedException { Department department = (Department)super.clone(); List<Employee> emps = new ArrayList<>(); for (int i = 0; i < department.employees.size(); i ++) { emps.add((Employee) employees.get(i).clone()); } department.employees = emps; return department; } }
public class Employee implements Cloneable { private String name; private String sex; private int age; private String country; private String province; private String city; private String post;
public Employee(String name, String sex, int age, String country, String province, String city, String post) { this.name = name; this.sex = sex; this.age = age; this.country = country; this.province = province; this.city = city; this.post = post; }
@Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
|
使用clone()
拷贝对象,因为类以及类中的属性也重写了clone()
。
1 2 3 4 5 6 7 8 9
| public class Client { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("张三", "男", 15, "中国", "江西省", "南昌市", "124-1241-1353"); Department department = new Department("开发部", "中国", "江西省", "南昌市", List.of(e)); Department department1 = (Department)department.clone(); System.out.println(department == department1); System.out.println(department.employees == department1.employees); } }
|
虽然这种方式可以深拷贝,但是这会让代码量激增。
序列化与反序列化可以解决这个问题。
修改版v3(序列化与反序列化)(推荐使用)
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
| public class Department { private String name; private String country; private String province; private String city; private List<Employee> employees;
public Department(String name, String country, String province, String city, List<Employee> employees) { this.name = name; this.country = country; this.province = province; this.city = city; this.employees = employees; } }
public class Employee { private String name; private String sex; private int age; private String country; private String province; private String city; private String post;
public Employee(String name, String sex, int age, String country, String province, String city, String post) { this.name = name; this.sex = sex; this.age = age; this.country = country; this.province = province; this.city = city; this.post = post; } }
|
序列化与反序列化的实现方式有很多种,本文使用Gson
来实现。以下是样例。
1 2 3 4 5 6 7 8 9 10 11
| public Client { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("张三", "男", 15, "中国", "江西省", "南昌市", "124-1241-1353"); Department department = new Department("开发部", "中国", "江西省", "南昌市", List.of(e)); Gson gson = new Gson(); String s = gson.toJson(department); Department department1 = s.fromJson(s, Department.class); System.out.println(department == department1); System.out.println(department.employees == department1.employees); } }
|
基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
总结
优点
缺点
适用场景