浅拷贝和深拷贝

浅拷贝

Java的深拷贝和浅拷贝,其实现方式正是通过调用Object类的clone()方法来完成

protected Object clone()
                throws CloneNotSupportedException创建并返回此对象的副本。 “复制”的精确含义可能取决于对象的类。 一般的意图是,对于任何对象x ,表达式: 
 x.clone() != x将是真实的,而且表达: 
 x.clone().getClass() == x.getClass()将是true ,但这些都不是绝对的要求。 通常情况是: 
 x.clone().equals(x)将是true ,这不是一个绝对的要求。 
按照惯例,返回的对象应该通过调用super.clone获得。 如果一个类和它的所有超类(除了Object )遵守这个惯例,那将是x.clone().getClass() == x.getClass()的情况。 

按照惯例,此方法返回的对象应该与此对象(正被克隆)无关。 为了实现这一独立性,可能需要修改super.clone返回的对象的一个或多个字段。 通常,这意味着复制构成被克隆的对象的内部“深层结构”的任何可变对象,并通过引用该副本替换对这些对象的引用。 如果一个类仅包含原始字段或对不可变对象的引用,则通常情况下, super.clone返回的对象中的字段通常不需要修改。 

clone的方法Object执行特定的克隆操作。 首先,如果此对象的类不实现接口Cloneable ,则抛出CloneNotSupportedException 。 请注意,所有数组都被认为是实现接口Cloneable ,并且数组类型T[]的clone方法的返回类型是T[] ,其中T是任何引用或原始类型。 否则,该方法将创建该对象的类的新实例,并将其所有字段初始化为完全符合该对象的相应字段的内容,就像通过赋值一样。 这些字段的内容本身不被克隆。 因此,该方法执行该对象的“浅拷贝”,而不是“深度拷贝”操作。 

Object类本身并不实现接口Cloneable ,因此在类别为Object的对象上调用clone方法将导致运行时抛出异常。 

结果 
这个实例的一个克隆。 
异常 
CloneNotSupportedException - 如果对象的类不支持Cloneable接口。 覆盖clone方法的子类也可以抛出此异常以指示实例无法克隆。

demo:

package com.al.o2o.demo;
/**
 * @author Xiahuicheng
 * 既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝
 * Address implements Cloneable
 */
public class Address  {
    private String provices;
    private String city;
    public void setAddress(String provices,String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }


}
package com.al.o2o.demo;

public class Person implements Cloneable {
    public String pname;
    public int page;
    public Address address;


    public Person() {}

    public Person(String pname,int page){
        this.pname = pname;
        this.page = page;
        this.address = new Address();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void setAddress(String provices,String city ){
        address.setAddress(provices, city);
    }
    public void display(String name){
        System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+ address);
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

}
  • 这是一个我们要进行赋值的原始类 Person。下面我们产生一个 Person 对象,并调用其 clone 方法复制一个新的对象。

注意:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法

 @Test
    public void testShallClone() throws Exception{

        Person p1 = new Person("zhangsan",21);
        p1.setAddress("湖北","武汉");
        Person p2 = (Person) p1.clone();
        System.out.println("p1_code:"+p1);
        System.out.println("p1pname:"+p1.getPname().hashCode());
        System.out.println("p2_code:"+p2);
        System.out.println("p1pname:"+p2.getPname().hashCode());

        p1.display("p1");
        p2.display("p2");
        p2.setAddress("湖北","荆州");
        System.out.println("修改地址后");
        p1.display("p1");
        p2.display("p2");
        
执行结果:
p1_code:com.al.o2o.demo.Person@15615099			//1
p1pname:-1432604556								//2
p2_code:com.al.o2o.demo.Person@1edf1c96			//3
p1pname:-1432604556								//4
p1:pname=zhangsan, page=21,Address [provices=湖北, city=武汉]		//5
p2:pname=zhangsan, page=21,Address [provices=湖北, city=武汉]		//6
修改地址后
p1:pname=zhangsan, page=21,Address [provices=湖北, city=荆州]		//7
p2:pname=zhangsan, page=21,Address [provices=湖北, city=荆州]		//8
  • 首先看原始类Person实现了Cloneable接口,并且覆写了clone方法,它还有三个属性,一个引用类型 String定义的 pname,一个基本类型 int定义的 page,还有一个引用类型 Address ,这是一个自定义类,这个类也包含两个属性 provices 和 city  接着看测试内容,首先我们创建一个Person 类的对象 p1,其pname 为zhangsan,page为21,地址类 Address 两个属性为 湖北省和武汉市。接着我们调用 clone() 方法复制另一个对象 p2,接着打印这两个对象的内容。

    从第 1 行和第 3 行打印结果:

    p1_code:com.al.o2o.demo.Person@15615099

    p2_code:com.al.o2o.demo.Person@1edf1c96

    可以看出这是两个不同的对象。

    从第 5 行和第 6 行打印的对象内容看,原对象 p1 和克隆出来的对象 p2 内容完全相同。

    代码中我们只是更改了克隆对象 p2 的属性 Address 为湖北省荆州市(原对象 p1 是 湖北省武汉市) ,但是从第 7 行和第 8 行打印结果来看,原对象 p1 和克隆对象 p2 的 Address 属性都被修改了。

    也就是说对象 Person 的属性 Address,经过 clone 之后,其实只是复制了其引用,他们指向的还是同一块堆内存空间,当修改其中一个对象的属性 Address,另一个也会跟着变化。

在这里插入图片描述

浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

深拷贝

深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

在这里插入图片描述

如何实现深拷贝

  1. 让每个引用类型属性内部都重写clone() 方法

既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Person 类有一个引用类型 Address(其实String 也是引用类型,但是String类型有点特殊,后面会详细讲解),我们在 Address 类内部也重写 clone 方法。如下:

 ```java
 public class Address implements Cloneable{
  4     private String provices;
  5     private String city;
  6     public void setAddress(String provices,String city){
  7         this.provices = provices;
  8         this.city = city;
  9     }
 10     @Override
 11     public String toString() {
 12         return "Address [provices=" + provices + ", city=" + city + "]";
 13     }
 14     @Override
 15     protected Object clone() throws CloneNotSupportedException {
 16         return super.clone();
 17     }
 18     
 19 }
 ```

 

 ```java
 1   //Person.class的clone()方法  
     @Override
 2     protected Object clone() throws CloneNotSupportedException {
 3         Person p = (Person) super.clone();
 4         p.address = (Address) address.clone();
 5         return p;
 6     }
 ```

 - 测试还是和上面一样,我们会发现更改了p2对象的Address属性,p1 对象的 Address 属性并没有变化。但是这种做法有个弊端,这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
  1. 利用序列化

    序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

    注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外

        public static void main(String[] args) throws IOException {
            ObjectOutputStream oos = null;
            ObjectInputStream ois = null;
    
            try {
                // 创建原始的可序列化对象
                Person p1 = new Person("ZHANGSHAN",21);
                p1.setAddress("湖北","武汉");
                System.out.println("p1:"+p1);
                p1.display("p1");
                Person p2 = null;
    
                //通过序列化实现深拷贝
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(bos);
                // 序列化以及传递这个对象
                oos.writeObject(p1);
                oos.flush();
                ByteArrayInputStream bin =new ByteArrayInputStream(bos.toByteArray());
                ois = new ObjectInputStream(bin);
                // 返回新的对象
                p2 = (Person) ois.readObject();
                System.out.println("p2:"+p2);
                p2.display("p2");
                //改变p2地址
                System.out.println("改变p2对象的内容");
                p2.setAddress("湖南","长沙");
                //查看每一个现在的内容
                p1.display("p1");
                p2.display("p2");
            }catch (Exception e){
                System.out.println("Exception in main =" +e);
            }finally {
                oos.close();
                ois.close();
            }
    
        }
    
    runting:
    
    p1:com.al.o2o.demo.Person@452b3a41
    p1:pname=ZHANGSHAN, page=21,Address [provices=湖北, city=武汉]
    p2:com.al.o2o.demo.Person@254989ff
    p2:pname=ZHANGSHAN, page=21,Address [provices=湖北, city=武汉]
    改变p2对象的内容
    p1:pname=ZHANGSHAN, page=21,Address [provices=湖北, city=武汉]
    p2:pname=ZHANGSHAN, page=21,Address [provices=湖南, city=长沙]
    

    因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。

基本类型和引用类型

  1. 在Java中可以分为两大类:基本类型和引用类型
  2. 基本类型也称为值类型,分别是字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double
  3. 引用类型包括类、接口、数组、枚举等等
  4. java将内存空间分为堆和栈,基本类型直接在栈中存储数值,而引用类型是将引用放到栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。

在这里插入图片描述

  • 上图定义的 a 和 b 都是基本类型,其值是直接存放在栈中的;而 c 和 d 是 String 声明的,这是一个引用类型,引用地址是存放在 栈中,然后指向堆的内存空间。下面 d = c;这条语句表示将 c 的引用赋值给 d,那么 c 和 d 将指向同一块堆内存空间

https://www.cnblogs.com/ysocean/p/8482979.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值