zoukankan      html  css  js  c++  java
  • 深拷贝+浅拷贝


    implements cloneable ,(实现此接口表示该java类支持克隆,如果没有实现此接口就


    调用了clone方法,则会报出来clonenotsupportedexception异常)


     覆写object类中的clone(),并将可见性从protect改为public


     浅拷贝和深拷贝出来新类


     考虑原类和新类的 成员变量是否独立


    (两种类型:primitive类型成员变量,引用类型成员变量)


    浅拷贝和深拷贝的区别:类中的引用对象是否也implements cloneable, 是否也覆写了


    object类中的clone()


    相同点:原类implements cloneable, 是否也覆写了object类中的clone()


    预备知识?????
    java的类型,java的类型分为两大类,一类为primitive,如int,另一类为引用类型,如String,Object等等
    java引用类型的存储,java的引用类型都是存储在堆上的。

    1.浅复制与深复制概念
    ⑴浅复制(浅克隆)
    被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
    ⑵深复制(深克隆)
    被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

    Java代码
        public class B

     {
        int a;
        String b;
         public B(int a, String b)

        {
            super();
            this.a = a;
            this.b = b;
        }
    }
    对这样一个引用类型的实例,我们可以推测,在堆上它的内存存储形式(除去指向class的引用,锁的管理等等内务事务所占内存),应该有一个int值表示a,以及一个引用,该引用指向b在堆上的存储空间。        

    为什么要clone?????
    有名的GoF设计模式里有一个模式为原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
    2.Java的clone()方法
    ⑴clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:
    ①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
    ②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样,都是同一个类new出来的不同对象!!!
    ③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
    ⑵Java中对象的克隆
    ①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
    ②在派生类中覆盖基类的clone()方法,并声明为public。
    ③在派生类的clone()方法中,调用super.clone()。
    ④在派生类中实现Cloneable接口。

    一般而言,我们要的clone应该是这样的。copy和原型的内容一样,但是又是彼此隔离的。即在clone之后,改变其中一个不影响另外一个。

    Object的clone以及为什么如此实现????????
    Object的clone的行为是最简单的。以堆上的内存存储解释的话(不计内务内存),对一个对象a的clone就是在堆上分配一个和a在堆上所占存储空间一样大的一块地方,然后把a的堆上内存的内容复制到这个新分配的内存空间上。
    看例子。

     java.lang.Cloneable 接口是一个空接口,该接口用来指明一个对象是否可以进行克隆.实现了该接口的对象可以调用clone()方法来进行对象的浅克隆.

    通过调用父类的super.clone()方法(浅拷贝)可以重新生成一个对象,解决因对象引用赋值造成的原对象的修改.

    ①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。 
    继承自java.lang.Object类的clone()方法是浅复制。以下代码可以证明之。


    浅拷贝(考虑成员变量是否独立)


    类的浅拷贝出来新的类


    primitive类型成员变量在两个类中是独立的,虽然值是相等的,但

    是改变其中一个类中的primitive值不会影响另一个类的primitive值。


    引用类型成员变量在两个类中是同一个,也是同一个堆空间,

    即:改变其中一个类中的引用值,也就是改变了另一个类的引用值。

    class User 
    {
        String name;
        int age;
    }

    class Account
    implements Cloneable 
    {
        User user;
        long balance;

        @Override
        public
    Object clone()throws CloneNotSupportedException
       {
            return super.clone();
        }
    }
    //下面是测试类里的代码
    User user = new User(); 
     user.name = "user"; 
     user.age = 20;
     Account account = new Account(); 
     account.user = user; 
     account.balance = 10000; 
     // copy. 
     Account copy = (Account) account.clone(); 
     
     // balance因为是primitive,所以copy和原型是相等且独立的。 
     Assert.assertEquals(copy.balance,account.balance); //为真!
     copy.balance =20000;
    // 改变copy不影响primitive类型。 
     Assert.assertTrue(copy.balance != account.balance); //为真!
      
     // user因为是引用类型,所以copy和原型的引用是同一的。 
     Assert.assertTrue(copy.user == account.user); //为真!
     copy.user.age = 22; 
     // 改变的是同一个东西。 
     Assert.assertEquals(copy.user.age, account.user.age); //为真!



    又一例子如下:

     class User implements Cloneable 


            String name; 
            int age; 
         
            @Override 
            publicObject clone() throws CloneNotSupportedException

     { 
                return super.clone(); 
            } 
        } 

    //测试类如下:

    // user.  
    User user = new User();  
    user.name = "user";  
    user.age = 20;  
       
     // copy 
     User copy = (User) user.clone(); 
      
    // age因为是primitive,所以copy和原型是相等且独立的。 
     Assert.assertEquals(copy.age, user.age); //为真!
     copy.age = 30; 
     // 改变copy不影响原型。 
     Assert.assertTrue(copy.age != user.age); //为真
      
     // name因为是引用类型,所以copy和原型的引用是同一的。 
     Assert.assertTrue(copy.name == user.name); //为真!
     // String为不可变类。没有办法可以通过对copy.name的字符串的操作改变这个字符串。 
     // 改变引用新的对象不会影响原型。 
     copy.name = "newname"; //这里应该会报错吧
     Assert.assertEquals("newname", copy.name); //为假,因为string类型不可变!
     Assert.assertEquals("user", user.name); //为真!
     可见,在考虑浅clone时,primitive和不可变对象类型是可以同等对待的。


    深拷贝()

    类的深拷贝出来新的类


    primitive类型成员变量在两个类中是独立的,虽然值是相等的,但

    是改变其中一个类中的primitive值不会影响另一个类的primitive

    值。


    引用类型成员变量在两个类中是独立的,是两个不同的堆空间,

    即:改变其中一个类中的引用值不会影响另一个类的引用类型。


    深拷贝的实现方法:把引用对象也拷贝一次,那么这就是深拷贝!也就是把全部引用对象都浅拷贝一次,就得到深拷贝

    class User implements Cloneable {
                String name;
                int age;

                @Override
                public User clone() throws CloneNotSupportedException {
                        return (User) super.clone();
                }
        }

        class Account implements Cloneable {
                User user;
                long balance;

                @Override
                public Account clone() throws CloneNotSupportedException {
                        Account account = null;

                        account = (Account) super.clone();
                        if (user != null) {
                                account.user = user.clone();
                        }

                        return account;
                }
        }




    3.利用串行化来做深复制
    把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。

    应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
    在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
    如下为深复制源代码。
    public Object deepClone()    
    {    
    //将对象写到流里    
    ByteArrayOutoutStream bo=new ByteArrayOutputStream();    
    ObjectOutputStream oo=new ObjectOutputStream(bo);    
    oo.writeObject(this);    
    //从流里读出来    
    ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());    
    ObjectInputStream oi=new ObjectInputStream(bi);    
    return(oi.readObject());    
    }   

    这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。
    class Teacher implements Serializable{
    String name;
    int age;
    Teacher(String name,int age){
    this.name=name;
    this.age=age;
    }
    }
    class Student implements Serializable{
    String name;//常量对象
    int age;
    Teacher t;//学生1和学生2的引用值都是一样的。
    Student(String name,int age,Teacher t){
    this.name=name;
    this.age=age;
    this.p=p;
    }
    public Object deepClone() throws IOException,
    OptionalDataException,ClassNotFoundException{//将对象写到流里
    ByteArrayOutoutStream bo=new ByteArrayOutputStream();
    ObjectOutputStream oo=new ObjectOutputStream(bo);
    oo.writeObject(this);//从流里读出来
    ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
    ObjectInputStream oi=new ObjectInputStream(bi);
    return(oi.readObject());
    }

    }
    public static void main(String[] args){ 
    Teacher t=new Teacher("tangliang",30);
    Student s1=new Student("zhangsan",18,t);
    Student s2=(Student)s1.deepClone();
    s2.t.name="tony";
    s2.t.age=40;
    System.out.println("name="+s1.t.name+","+"age="+s1.t.age);//学生1的老师不改变
    }


  • 相关阅读:
    Python量化分析,计算KDJ
    Ubuntu16.04安装Python3.6 和pip(python3 各版本切换)
    使用docker加载已有镜像安装Hyperledger Fabric v1.1.0
    Ubuntu 16.04将左侧面板置于底部
    解决Flask局域网内访问不了的问题
    Ubuntu 16.04 安装Go 1.9.2
    Ubuntu16.04下安装Hyperledger Fabric 1.0.0
    Ubuntu 16.04安装Docker-CE
    用Python抓取网页并解析
    图解python中赋值、浅拷贝、深拷贝的区别
  • 原文地址:https://www.cnblogs.com/baoendemao/p/3804682.html
Copyright © 2011-2022 走看看