美文网首页
Java深度拷贝

Java深度拷贝

作者: 饿o_o狼 | 来源:发表于2018-05-16 16:55 被阅读0次

    标签: Android 内存相关

    Why

    • 直接赋值无法断开引用
    • 浅拷贝在含有内部对象引用的时候无法断开引用
    • 断开引用防止内存溢出、防止同步修改

    1. 直接引用

    举例:

    
    public class A {
    
        public String name = "我是A";
    
    }
    
            A a = new A();
            A b = a;
            b.name = "我是B";
            System.out.println("a : " + a.name + "  b: " + b.name);
    
    

    结果: a : 我是B b: 我是B
    这就是直接引用,a和b引用的是同一个对象

    2. 浅拷贝

    举例:

    public class A implements Cloneable{
        public String name = "我是A";
        
        @Override
        protected Object clone(){
            Object o=null; 
              try
               { 
               o=(A)super.clone();//Object 中的clone()识别出你要复制的是哪一个对象。 
               } 
              catch(CloneNotSupportedException e) 
               { 
                System.out.println(e.toString()); 
               } 
              return o;
        }
    }
    
            A a = new A();
            A b = (A) a.clone();
            b.name = "我是B";
            System.out.println("a : " + a.name + "  b: " + b.name);
    

    结果: a : 我是A b: 我是B
    这个就是浅拷贝,看着a和b已经不是引用同一个对象了。到这里是不是觉得已经OK了,断开引用了就不需要在研究深拷贝了。如果仅是这种情况确实是可以了,但是A类中只有一个String的成员变量。

    我们看下这种情况

    public class A implements Cloneable{
        public String name = "我是A";
        public UserTest UserTest = new UserTest();
        
        @Override
        protected Object clone(){
            Object o=null; 
              try
               { 
               o=(A)super.clone();//Object 中的clone()识别出你要复制的是哪一个对象。 
               } 
              catch(CloneNotSupportedException e) 
               { 
                System.out.println(e.toString()); 
               } 
              return o;
        }
    }
    
            A a = new A();
            a.UserTest.test = "我是第一个";
            A b = (A) a.clone();
            b.name = "我是B";
            b.UserTest.test = "我是第二个";
            System.out.println("a : " + a.name + "  b: " + b.name);
            System.out.println("a test : " + a.UserTest.test + "  b test: " + b.UserTest.test);
    

    结果:
    a : 我是A b: 我是B
    a test : 我是第二个 b test: 我是第二个

    第一条打印发现a和b已经不是一个对象了,但是第二个打印发现a对象中的变量UserTest和b对象中的变量是同一个对象。这就是浅拷贝的优缺点。

    • 优点: 实现简单,在只含有基础引用类型的时候可以实现断开引用
    • 缺点: 当含有对象引用类型的时候,clone并不能复制内部对象

    3. 深度拷贝

    public class A implements Cloneable{
        public String name = "我是A";
        public UserTest UserTest = new UserTest();
        
        @Override
        protected Object clone() {
            A o = null;
            try {
                o = (A) super.clone();// Object 中的clone()识别出你要复制的是哪一个对象。
                o.UserTest = (test.field.deepclone.UserTest) UserTest.clone();
            } catch (CloneNotSupportedException e) {
                System.out.println(e.toString());
            }
            return o;
        }
    }
    
    public class UserTest implements Cloneable{
        
        public String test;
        
        @Override
        protected Object clone() {
            Object o = null;
            try {
                o = (UserTest) super.clone();
            } catch (CloneNotSupportedException e) {
                System.out.println(e.toString());
            }
            return o;
        }
    
    }
    
            A a = new A();
            a.UserTest.test = "我是第一个";
            A b = (A) a.clone();
            b.name = "我是B";
            b.UserTest.test = "我是第二个";
            System.out.println("a : " + a.name + "  b: " + b.name);
            System.out.println("a test : " + a.UserTest.test + "  b test: " + b.UserTest.test);
    

    结果:
    a : 我是A b: 我是B
    a test : 我是第一个 b test: 我是第二个
    深度拷贝是在浅拷贝的基础上对内部的非基本引用类型再次进行拷贝,这样的话就是完全的两个对象之间不存在关联引用。

    How

    实现方式:

    • 使用cloneCloneable接口逐级进行拷贝
    • 使用Serializable接口,将对象写入流中,在从流中重新读取生成新的对象
    • 使用Gson进行正反序列化,生成新的对象

    cloneCloneable

    如同上面例子使用,如果遇到有子对象就要连子对象也做clone,碰到列表等集合类要全都clone。

    使用Serializable接口

    例子:

    public class User implements Serializable{
        
        public String name;
        public String age;
        
        public UserTest test;
    
        public Object deepClone(){
            try {
                ByteArrayOutputStream bo = new ByteArrayOutputStream();
                ObjectOutputStream os = new ObjectOutputStream(bo);
                os.writeObject(this);
    
                ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
                ObjectInputStream is = new ObjectInputStream(bi);
    
                return is.readObject();
            } catch (Exception e) {
                System.out.println("失败了:" + e.getMessage());
            }
            return null;
        }
    
    
    }
    
    
    public class UserTest implements Serializable{
        
        public String test;
    }
    
    

    通过Serializable接口将对象写入带流中,然后在读取回来生成新的对象。对于子类也是需要依赖Serializable接口的,否则会序列化失败。

    Gson序列化

    这个比较简单,通过以下代码可以看得出来使用方式。

    public User deep() {
            Gson gson = new Gson();
            return gson.fromJson(gson.toJson(this), User.class);
        }
    

    只需要通过Gson将对象转换成json字符,然后再将json转换回对象。对于内部对象没有要求。

    Compare

    对比 clone Serializable Gson
    易用性 在只含有基础引用对象的时候非常好用,在含有集合或者多层级的内部对象的时候非常复杂 只需要在该类本身或者内部对象类上加上这个接口就可以了使用简单,对于集合不需要做特殊处理 只需要两行代码不需要考虑是否含有内部对象
    扩展性 扩展内部对象需要依次实现clone方法,不方便,对于集合扩展很麻烦 扩展需要依次引用Serializable接口 不需要变动代码
    性能 clone 10000条耗时:4ms Serializable 10000条耗时:583ms gson 10000条耗时:868ms

    通过上述表格可以得出以上三种方法的优缺点和使用场景。

    • clone:优点:适合于内部只有基础引用对象的类,系统出品性能非常好。可以做多次循环等频繁操作。
      缺点:含有集合变量的类不适合使用,容易变动的类也不适合使用。
    • Serializable:比较中庸,比clone的优点差一点,比clone的缺点好一点,在没有太多层级引用关系并且有集合变量的时候比clone要适合使用
    • Gson:优点:可以作为工具类,实现简单就两行代码不需要担心对象的结构,非常方便。
      缺点:消耗性能比较大,不适合频繁操作,只适合单次或者少量的使用。

    相关文章

      网友评论

          本文标题:Java深度拷贝

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