美文网首页
Java复制

Java复制

作者: 云芈山人 | 来源:发表于2021-07-06 14:06 被阅读0次

概述

Java复制分为三类:直接赋值、浅拷贝和深拷贝

Java复制是基于Object的clone()方法。

/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;

由此可见,clone()是一个native方法(非Java代码实现,供Java调用),因为Java程序运行在JVM中,想要访问比较底层的与操作系统相关的,只能由靠近操作系统的语言来实现。

  1. 第一次声明保证克隆对象就有单独的内存地址分配。
  2. 第二次声明表明,原始和克隆对象应该具有相同的类类型,但它并非强制额。
  3. 第三次声明表明,原始和克隆对象应该是平等的equals()方法使用,但它不是强制性的。

每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但该方法是protected,所以不能在类外进行访问。想对一个对象复制,需要对clone对象进行覆盖。

通过clone方法赋值的对象跟原来的对象时同时独立存在的。

直接赋值

在 Java 中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟着变化。

浅拷贝(ShollowClone)

  • 复制引用,但不复制引用指向的对象。
  • 在浅复制中,创建一个对象,如果原型对象的成员变量是值类型,则复制一份给克隆对象。如果原型对象中的成员变量是引用类型,则将引用对象的地址复制给克隆对象,而不复制引用对象,即原型对象和克隆对象的引用类型成员变量指向同一对象
  • 图解


    浅拷贝.png
  • 实现
    1. 被复制的类需实现Cloneable接口(否则调用clone()方法时会报出CloneNotSupportedException异常)。
    2. 覆盖clone()方法,并将修饰符改为public。方法中调用super.clone()【native方法】得到原型对象。

Java语言提供的Cloneable接口Serializable接口的代码非常简单,只是空接口,也称标识接口,没有任何方法的定义,其作用是告诉JRE这些接口,是否具有这些功能,比如是否支持复制,是否支持序列化等。

  • 例子
public Class Orders implements Cloneable {
    private String oid;
    private String ordername;
    private User user;
    ...
   
    public  Object clone(){
      try { 
         return (Orders)super.clone(); 
      } catch (Exception e) { 
         e.printStackTrace(); 
         return null; 
      }
    }
}

深拷贝(DeepClone)

  • 复制对象及其引用的对象
  • 在深拷贝中,无论值类型或者引用类型成员变量都将拷贝一份给克隆对象
  • 图解


    深拷贝.png
  • 实现
    1. 方法一:实现Cloneable接口,并覆盖Object的clone()方法。
public Class User implements Cloneable {
    private String userid;
    private String username;
    ...
   
    public  Object clone(){
      try { 
         return (User )super.clone(); 
      } catch (Exception e) { 
         e.printStackTrace(); 
         return null; 
      }
    }
}

public Class Orders implements Cloneable {
    private String oid;
    private String ordername;
    private User user;
    ...
   
    public  Object clone(){
      Orders  orders = null;
      try { 
         orders = (Orders)super.clone(); 
      } catch (Exception e) { 
         e.printStackTrace(); 
      }
      orders.user = (User)user.clone();
      return orders;
    }
}
  1. 方法二:通过序列化(解决多层复制问题)
    如果引用类型中还包含着很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone就会很麻烦,可用序列化来实现深度复制。

序列化就是将对象写到流的过程,写到流中的对象时原有对象的拷贝,而原对象仍在内存中。通过序列化实现的拷贝,不仅可以复制其对象本身,还可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深拷贝。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

public class Outer implements Serializable{
   private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID
   public Inner inner;
  //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] 
   public Outer myclone() {
       Outer outer = null;
       try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
           ByteArrayOutputStream baos = new ByteArrayOutputStream();
           ObjectOutputStream oos = new ObjectOutputStream(baos);
           oos.writeObject(this);
       // 将流序列化成对象
           ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
           ObjectInputStream ois = new ObjectInputStream(bais);
           outer = (Outer) ois.readObject();
       } catch (IOException e) {
          e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
       return outer;
   }
 }

public class Inner implements Serializable{
   private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
  public String name = "";
  
   public Inner(String name) {
      this.name = name;
  }
 
  @Override
   public String toString() {
      return "Inner的name值为:" + name;
   }
 }

总结

  1. 实现Cloneable接口并重写Object类中的clone()方法;
  2. 实现Serializable接口,通过对象的序列化和反序列化实现拷贝,可以实现真正的深度拷贝。

基于序列化和反序列化实现的拷贝不仅是深度拷贝,更重要的是通过泛型限定,可以检查出要拷贝的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种方案明显优于使用Object类的clone方法拷贝对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

相关文章

网友评论

      本文标题:Java复制

      本文链接:https://www.haomeiwen.com/subject/znhgultx.html