Java中的clone方法支持深克隆吗?

Java
237
0
0
2024-01-09
  1. clone的使用条件
  2. clone不会调用构造器
  3. 深克隆与浅克隆
  4. 深克隆场景一:基本数据类型
  5. 深克隆场景二:包装器类型
  6. 深克隆场景三:字符串类型
  7. 深克隆场景四:数组类型
  8. 总结

clone的使用条件

Object类中的clone方法的作用主要是当调用clone方法时,会创建这个对象的一个拷贝并返回。定义如下:

  protected native Object clone() throws CloneNotSupported Exception ; 

如果想成功调用一个类的clone方法,这个类必须实现 java.lang .Cloneable接口,如果没有实现这个接口,运行时会抛出CloneNotSupportedException异常。如下图,A类是client的静态嵌套类,重写了clone方法但是没有实现 java .lang.Cloneable接口,运行时直接抛出CloneNotSupportedException。

CloneNotSupportedException

所有的数组都是实现了java.lang.Cloneable接口的,可以直接调用clone进行克隆。

clone不会调用构造器

我们在 Client 的静态嵌套类A中的 构造器 执行一个打印语句,在main方法中创建一个对象a,然后再克隆一份,可以看到打印语句只打印了一个120,说明clone方法的执行并不会调用构造器,如果clone方法会调用构造器的话,打印语句应该会执行2次才对。如图:

深克隆与浅克隆

浅克隆是指仅仅克隆这个对象,而不克隆这个对象所引用的对象,原对象和新克隆的对象所引用的对象是同一个对象。

深克隆是指不仅仅克隆这个对象,也克隆这个对象所引用的对象,原对象和新克隆的对象所引用的对象不是同一个对象。

在实际当中使用深克隆的目的通常并不是为了把这个对象和这个对象的所有字段都克隆一遍,主要是为了避免原对象对字段的更改也会更改掉克隆出的对象中的字段,即避免克隆出的对象依赖于被克隆的对象。为了实现这种独立性clone方法在某些场景下是可以满足的。

深克隆场景一:基本数据类型

如果一个类中的字段都是基本数据类型的话,那么clone方法是可以实现深克隆的。因为基本数据类型不是引用类型,克隆时是直接复制的值本身,而不是复制了一个引用。

如下图类A,包含了8种基本类型的字段并且都具有初始值。除此之外还重写了equals和 hashCode 方法,主要目的在于使用equals方法判断一个对象的这8种基本类型是否相等。

  import java.util.Objects;
 
 public class A implements Cloneable {
      private   byte  aByte = 1;
     private  short  aShort = 1;
     private int anInt =;
     private long aLong =L;
     private float aFloat =.0f;
     private double aDouble =.0d;
     private  Boolean  aBoolean = true;
     private char aChar = 'a';
 
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         A a = (A) o;
         return aByte == a.aByte && aShort == a.aShort 
                 && anInt == a.anInt && aLong == a.aLong 
                 && Float.compare(a.aFloat, aFloat) == 
                 && Double.compare(a.aDouble, aDouble) == 
                 && aBoolean == a.aBoolean && aChar == a.aChar;
    }
 
     @Override
     public int hashCode() {
         return Objects.hash(aByte, aShort, anInt, aLong, aFloat, aDouble, aBoolean, aChar);
    }
 
     @Override
     protected Object clone() throws CloneNotSupportedException {
         return super.clone();
    }
 
     public byte getaByte() {
         return aByte;
    }
 
     public  void  setaByte(byte aByte) {
         this.aByte = aByte;
    }
 
     public short getaShort() {
         return aShort;
    }
 
     public void setaShort(short aShort) {
         this.aShort = aShort;
    }
 
     public int getAnInt() {
         return anInt;
    }
 
     public void setAnInt(int anInt) {
         this.anInt = anInt;
    }
 
     public long getaLong() {
         return aLong;
    }
 
     public void setaLong(long aLong) {
         this.aLong = aLong;
    }
 
     public float getaFloat() {
         return aFloat;
    }
 
     public void setaFloat(float aFloat) {
         this.aFloat = aFloat;
    }
 
     public double getaDouble() {
         return aDouble;
    }
 
     public void setaDouble(double aDouble) {
         this.aDouble = aDouble;
    }
 
     public boolean isaBoolean() {
         return aBoolean;
    }
 
     public void setaBoolean(boolean aBoolean) {
         this.aBoolean = aBoolean;
    }
 
     public char getaChar() {
         return aChar;
    }
 
     public void setaChar(char aChar) {
         this.aChar = aChar;
    }
 } 

在如下的Client类中创建一个A类的对象a1并克隆出一个对象a2,执行main方法后看出a1==a2为false,说明a1和a2不是同一个对象;a1.equals(a2)为true,说明克隆出来的对象和原对象的字段也都是相等的。

代码中的10-12行重新设置了a1中的3个值,14-16行用于验证a2中的值是否也进行了更改。

从执行结果中可以看出a2的结果不受a1更改的影响,说明此时深克隆是成功的。

深克隆场景二:包装器类型

如果一个类中的字段都是基本数据类型的包装器类型的话,那么clone方法是可以实现深克隆的。虽然包装器类是一个引用类型的类,复制之后指向的也是同一个包装器对象,但是由于包装器类不可变,也是可以达到深克隆的目的的。

Integer 为int基本数据类型的包装器。如下代码A类中只有一个Integer类型的属性age。

  import java.util.Objects;
 
 public class A implements Cloneable {
 
     private Integer age;
 
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         A a = (A) o;
         return Objects.equals(age, a.age);
    }
     @Override
     public int hashCode() {
         return Objects.hash(age);
    }
     @Override
     protected Object clone() throws CloneNotSupportedException {
         return super.clone();
    }
 
     public Integer getAge() {
         return age;
    }
 
     public void setAge(Integer age) {
         this.age = age;
    }
 } 

在如下的Client类中创建一个A类的对象a1并克隆出一个对象a2,执行main方法后看出a1==a2为false,说明a1和a2不是同一个对象;a1.equals(a2)为true,说明克隆出来的对象和原对象的字段也都是相等的。

第9行的打印结果也是true,说明克隆后的对象a2的age和a1的age指向的是同一个Integer对象。

第12行取出a1对象的age,但是无法对其进行更改,a1的age和a2的age永远都是相等的,也是可以达到深克隆的目的。

深克隆场景三: 字符串 类型

如果一个类中的字段都是字符串类型的话,那么clone方法是可以实现深克隆的。虽然 String类 是一个引用类型的类,复制之后指向的也是同一个String对象,但是由于String类不可变,也是可以达到深克隆的目的的。

如下代码,A类只有一个字符串类型的字段name。

  import java.util.Objects;
 
 public class A implements Cloneable {
 
     private String name;
 
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         A a = (A) o;
         return Objects.equals(name, a.name);
    }
 
     @Override
     public int hashCode() {
         return Objects.hash(name);
    }
 
     @Override
     protected Object clone() throws CloneNotSupportedException {
         return super.clone();
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 } 

在如下的Client类中创建一个A类的对象a1并设置其name为abc,然后克隆出一个对象a2,执行main方法后看出a1==a2为false,说明a1和a2不是同一个对象;a1.equals(a2)为true,说明克隆出来的对象和原对象的字段也都是相等的。

第9行代码也是true,说明a1和a2对象的name指向的也是同一个对象。

第12行代码我们调用了String类的 replace 方法,然后打印a1对象和a2对象的name,结果显示结果任然为abc。这主要是因为String类不可变,replace方法也只是会返回替换后的结果,原来的值并不会改变。

深克隆场景四:数组类型

由于基本数据类型、包装器类型和String类型支持深拷贝,所以其对应的数组也都是可以进行深拷贝的,拷贝之后的新数组独立于原来的数组。

如图,string1数组和string2数组相互独立,互不影响。

总结

以上就是直接调用Object类中clone方法支持的深拷贝的场景,总结下来就是如果一个对象的字段是基本数据类型、不可变对象或者基本数据类型和不可变对象的数组,都是可以直接调用Object类中clone方法进行深克隆的。对于基本数据类型和不可变对象的数组也是可以的。

不可变对象不仅仅是包装器类和String类,其他不可变对象或者自定义的不可变对象也是同样适用的。