欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java深拷贝和浅拷贝的区别

程序员文章站 2022-07-06 09:14:08
...
首先我们看看浅拷贝和深拷贝的定义

浅拷贝:只复制一个对象,对象内部存在的指向其他对象数组或者引用则不复制
深拷贝:对象,对象内部的引用均复制
  为了更好的理解它们的区别我们假设有一个对象A,它包含有2对象,对象A1和对象A2
  对象A进行浅拷贝后,得到对象B但是对象A1和A2并没有被拷贝
  对象A进行深拷贝,得到对象B的同时A1和A2连同它们的引用也被拷贝
  在理解了深拷贝和浅拷贝后,我们来看看Java的深拷贝和浅拷贝实现.

Object 类的 clone方法执行特定的克隆操作。
首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。(注意:所有的数组都被视为实现接口 Cloneable)
此方法会创建此对象的类的一个新实例,并像通过分配,严格使用此对象相应字段的内容初始化该对象的所有字段;这些字段的内容没有被自我克隆。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。
Object 类本身不实现接口 Cloneable,所以在类为 Object的对象上调用 clone 方法将会导致在运行时抛出异常。

下面从三个复制的实例代码来看它们之间的区别,我们需建立Person类:

public class Person{
   private String name;
   private int age;
   public Person(String name, int age) {
       this.name = name;
       this.age = age;
    }
   public void setAge(int age) {
       this.age = age;
    }
   public void setName(String name) {
       this.name = name;
    }
   public void display() {
       System.out.println("Name:" + name + "/tAge:" + age);
    }
}


一、普通复制
即我们最容易想到的将一个对象赋值给另外一个对象。
public static void main(String[] args) {
       Person p1=new Person("jack",20);
       Person p2=p1;
       p1.setAge(49);//简单复制
        p2.display();
       p1.display();
       System.out.println(p1);
       System.out.println(p2);
    }


结果:        Name:jack    Age:49
               Name:jack   Age:49
               net.pcedu.clone.Person@c17164
               net.pcedu.clone.Person@c17164
说明p1和p2对象的是同一个引用,所以再改属性2个都是改变的。

二、浅拷贝
浅拷贝必需对Book类实现Cloneable接口的clone方法
public class Book implements Cloneable{
         String bookName;
         double price;
         Person author;
         public Book(String bn,double price,Person author){
                bookName = bn;
                this.price = price;
                this.author = author;   
         }   
         public Object clone(){
           Book b = null;
           try{
               b = (Book)super.clone();
           }catch(CloneNotSupportedExceptione){
               e.printStackTrace();
           }
            return b;
         }
         public void display(){
             System.out.print(bookName + "/t" +price + "/t") ;
              author.display();   
         }
        }
publicstatic void main(Stringargs[]){
      Book b1 = new Book("Java编程",30.50,new Person("张三",34));
      Book b2 = (Book)b1.clone();
      b2.price = 44.0;
      b2.author.setAge(45);
      b2.author.setName("李四");
      b2.bookName = "Java开发";
      b1.display();
      b2.display();
    }


结果:
Java编程  30.5   Name:李四 Age:45
Java开发  44.0   Name:李四 Age:45
说明b1和b2是不同的对象,但是b1.author和b2.author指向同一对象,。

问题如下: 发现在改变b2的author对象属性时b1的author对象的属性也改变了,说明在浅拷贝中的author这个对象没有被完全拷贝,而是使用同一引用,这样就要使用深拷贝了。

三、深拷贝
为了解决如上问题,我们需要用到深拷贝,其实很简单在拷贝book对象的时候加入如下语句
b.author =(Person)author.clone(); //将Person对象进行拷贝,Person对象需进行了拷贝
在运行上面的main方法,结果如下:
  Java编程30.5   Name:张三 Age:34
Java开发44.0   Name:李四 Age:45
说明b1和b2是不同的对象,b1.author和b2.author指向不同对象,。(含引用对象属性的拷贝)。

java.lang.t的clone()方法默认是返回一个前拷贝对象。因此如果要用clone()方法实现一个深拷贝,我们必须对每个对象的clone()方法进行特别实现。当对象层次复杂的时候,这样做不但困难而且浪费时间和容易出现错误,特别有时候你不但需要深拷贝同时你也对这个对象进行浅拷贝的时候,你会发现写这个clone()方法真不是一个好的解决方案。

那么除了clone()方法,我们还可以怎么实现呢?答案是序列化
序列化的对象要实现Serializable接口才能实现序列化,同时,对象的成员对象也要实现序列化.
A a=new A();
//写对象,序列化  
 ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); 
 ObjectOutputStream out= new ObjectOutputStream(byteOut); 
 out.writeObject(a);
 //读对象,反序列化 
  ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
 A b=(A)in.readObject();

序列化经常用于文件传递的读取。尤其是在缓存中用得比较多,通过序列化可以将对象缓存在硬盘中。这在登录系统缓存用户权限和角色等信息最常见。而用对克隆对象,也不失为一种很好的方法。


java.lang.Object类的clone方法是一个protected方法,在子类需要重写此方法并声明为public类型,而且还需实现Cloneable接口才能提供对象复制的能力,clone()是一个native方法,native方法的效率一般来说都是远高于java中的非native方法,对性能比较关心的话首先考虑这种方式,另一种方式——通过java的反射机制复制对象,这种方式效率可能会比clone()低,而且不支持深度复制以及复制集合类型,但通用性会提高很多,下边是进行复制的代码:
private <T> T getBean(T TargetBean, T SourceBean) {
        if (TargetBean== null) return null;
        Field[] tFields = TargetBean.getClass().getDeclaredFields();
        Field[] sFields = SourceBean.getClass().getDeclaredFields();
        try {
            for (Field field : tFields ) {
                String fieldName = field.getName();
                if (fieldName.equals("serialVersionUID")) continue;
                if (field.getType() == Map.class) continue;

                if (field.getType() == Set.class) continue;

                if (field.getType() == List.class) continue;
                for (Field sField : sFields) {
                    if(!sField .getName().equals(fieldName)){
                        continue;
                    }
                    Class type = field.getType();
                    String setName = getSetMethodName(fieldName);
                    Method tMethod = TargetBean.getClass().getMethod(setName, new Class[]{type});
                    String getName = getGetMethodName(fieldName);
                    Method sMethod = SourceBean.getClass().getMethod(getName, null);
                    Object setterValue = voMethod.invoke(SourceBean, null);
                    tMethod.invoke(TargetBean, new Object[]{setterValue});
                }
            }
        } catch (Exception e) {
            throw new Exception("设置参数信息发生异常", e);
        }
        return TargetBean;
}
该方法接收两个参数,一个是复制的源对象——要复制的对象,一个是复制的目标对象——对象副本,当然这个方法也可以在两个不同对象间使用,这时候只要目标对象和对象具有一个或多个相同类型及名称的属性,那么就会把源对象的属性值赋给目标对象的属性。