是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。
简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示:
是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示:
需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法,实现代码如下:
/** * author:wy * describe:Java 实现克隆 */ @Data @NoArgsConstructor @AllArgsConstructor public class People implements Cloneable { public static void main(String[] args) { People p1 = new People(); p1.setId(1); p1.setName("Java"); People p2 = null; try { // 克隆p1对象 p2 = (People) p1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.println(p1);// People(id=1, name=Java) System.out.println(p2);// People(id=1, name=Java) System.out.println(p1 == p2);// false System.out.println(p1.getClass() == p2.getClass());// true System.out.println(p1.equals(p2));// true p1.setName("Android"); System.out.println(p1);// People(id=1, name=Android) System.out.println(p2);// People(id=1, name=Java) } // 属性 private Integer id; private String name; /** * 重写 Object 类中的 clone() 方法 * @return * @throws CloneNotSupportedException */ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
clone()
深克隆// 是使用 native 修饰的本地方法,因此执行的性能会很高 protected native Object clone() throws CloneNotSupportedException;
从以上源码的注释信息中我们可以看出,Object 对 clone() 方法的约定有三条:
- 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;
- 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的;
- Ba对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。
Arrays.copyOf()
浅克隆@Test public void test() { People[] o1 = {new People(1, "Java")}; People[] o2 = Arrays.copyOf(o1, o1.length); // 修改原型对象的第一个元素的值 o1[0].setName("Android"); System.out.println(o1[0]);// People(id=1, name=Android) System.out.println(o2[0]);// People(id=1, name=Android) }
因为数组比较特殊,数组本身就是引用类型,因此在使用
Arrays.copyOf()
其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了。
深克隆的实现方式有很多种,大体可以分为以下几类:
- 所有对象都实现克隆方法;
- 通过构造方法实现深克隆;
- 使用 JDK 自带的字节流实现深克隆;
- 使用第三方工具实现深克隆,比如 Apache Commons Lang;
- 使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。
/** * author:wy * describe:收货人 */ @Data @NoArgsConstructor @AllArgsConstructor public class Consignee implements Cloneable { public static void main(String[] args) throws CloneNotSupportedException { Address address = new Address(101, "上海"); Consignee c1 = new Consignee(1, "骑士", address); // 克隆 c1 对象 Consignee c2 = c1.clone(); // 修改原型对象 c1.getAddress().setCity("北京"); System.out.println(c1);// Consignee(id=1, name=骑士, address=Address(id=101, city=北京)) System.out.println(c2);// Consignee(id=1, name=骑士, address=Address(id=101, city=上海)) } private Integer id; private String name; private Address address;// 包含 Address 对象 @Override protected Consignee clone() throws CloneNotSupportedException { Consignee consignee = (Consignee) super.clone(); consignee.setAddress(this.address.clone());// 引用类型克隆赋值 return consignee; } }
/** * author:wy * describe:地址 */ @Data @NoArgsConstructor @AllArgsConstructor public class Address implements Cloneable { private Integer id; private String city; @Override protected Address clone() throws CloneNotSupportedException { return (Address) super.clone(); } }
从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆。
《Effective Java》 中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象,实现代码如下:
/** * author:wy * describe:收货人2 */ @Data @NoArgsConstructor @AllArgsConstructor public class Consignee2 { public static void main(String[] args) { Address2 address = new Address2(102, "北京"); Consignee2 c1 = new Consignee2(2, "骑士梦", address); // 调用构造函数克隆对象 Consignee2 c2 = new Consignee2(c1.getId(), c1.getName(), new Address2(c1.getAddress().getId(), c1.getAddress().getCity())); // 修改原型对象 c1.getAddress().setCity("上海"); System.out.println(c1);// Consignee2(id=2, name=骑士梦, address=Address2(id=102, city=上海)) System.out.println(c2);// Consignee2(id=2, name=骑士梦, address=Address2(id=102, city=北京)) } private Integer id; private String name; private Address2 address;// 包含 Address 对象 }
/** * author:wy * describe:地址2 */ @Data @NoArgsConstructor @AllArgsConstructor public class Address2 { private Integer id; private String city; }
从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆。