近况

最近挺忙的,忙着找房子,忙着看网课,时间还是很紧迫的,留给开发中学习的时间不太多,
公司中还需要学习项目上的业务等内容,当然,即使再忙,总要回顾一下曾经的日子,总要记录一点东西的习惯
是不曾改变的,复盘自己的过去可以帮助自己的未来更好的做选择。

Java的深浅拷贝

我之前看过相关的博客,我以前也懂得什么是深浅拷贝,但学习不应该停止在这个阶段,当我真正使用的时候
我却不知道如何更好的实现深浅拷贝,或者说有没有别人已经造好的,更优雅的方式实现呢?
这驱使我写下这篇文章,用来记录一下这部分内容。

  • 含义

    • 引用拷贝
      引用拷贝就是对象之间直接使用=进行赋值,这并不会产生新的对象,
      只是复制了对象的地址而已,两个变量指向的还是同一个对象。

    • 浅拷贝
      浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话
      浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。

    • 深拷贝

      深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

  • 深浅拷贝实现方式

    • 浅拷贝实现方式
      浅拷贝一般是需要实现cloneable接口,重写clone方法,在方法中调用父类的clone方法返回即可

      @Data
      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 class Address {
          private String provices;
          private String city;
          public void setAddress(String provices,String city){
              this.provices = provices;
              this.city = city;
          }
      }
      
      /*
        Test
        这个p2就是p1浅拷贝的对象,p2对象与p1并不相同,
        但p2和p1的address属性却是引用的同一个address对象
      */
      Person p1 = new Person("zhangsan",21);
      p1.setAddress("湖北省", "武汉市");
      Person p2 = (Person) p1.clone();

      当然,你也可以使用别人已经写好的工具类去得到浅拷贝对象
      例如spring的BeanUtilsapache的commons中的BeanUtilshutools的BeanUtil
      它们的copyProperties方法都能实现对象的浅拷贝。

    • 深拷贝实现方式
      实现深拷贝对象有三种方式

      1. 一种仍然是使用cloneable,即被引用的属性,如Address同样实现
        cloneable接口,重写clone方法,下面是案例改造:

        @Data
        public class Person implements Cloneable{
            public String pname;
            public int page;
            public Address address;
            public Person() {}
            public Person(String pname,int page,String provices,String city){
                this.pname = pname;
                this.page = page;
                this.address = new Address(provices,city);
            }
            @Override
            protected Object clone() throws CloneNotSupportedException {
               	Person person = (Person)super.clone();
                //对引用属性克隆,使得两个person对象的address引用对象也不相同,实现深拷贝
                person.address = (Address)address.clone();
                return person;
            }
            
        }
        
        @Data
        public class Address implements Cloneable {
            private String provices;
            private String city;
            
            @Override
            protected Object clone() throws CloneNotSupportedException {
                return super.clone();
            }
        }

        由上述案例可知,想要实现对象的深拷贝,就需要被拷贝对象及其引用属性都实现cloneable接口并重写clone方法
        这种实现深拷贝的缺点是,如果被拷贝对象的引用属性的属性仍然是个引用属性,这个对象仍然需要
        实现cloneable接口,这种多层都需要克隆的方式非常不美观,每一个对象都需要重写clone方法不优雅

      2. 那么另一种实现对象深拷贝的方式是序列化,序列化的实现方式要比cloneable方式要好上不少
        要想实现对象深拷贝,需要每一个对象都实现Serializable 接口,下面代码是对上述案例的改造:

        @Data
        public class Person implements Serializable {
            private static final long serialVersionUID = 369285298572941L;
            public String pname;
            public int page;
            public Address address;
            public Person() {}
            public Person(String pname,int page,String provices,String city){
                this.pname = pname;
                this.page = page;
                this.address = new Address(provices,city);
            }
            
            public Object clone(){
               	Person person = 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);
                    person = (Person) ois.readObject();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                return person;
            }
        }
        
        @Data
        public class Address implements Serializable {
            private static final long serialVersionUID = 872390113109L; 
            
            private String provices;
            private String city;
        }

        什么?这种深拷贝还是很麻烦?只要你的对象,对象的引用属性对象实现了Serializable接口,
        你可以使用hutools的ObjectUtil.cloneByStream(obj)方式,同样可以完成深拷贝。

      3. 最后一种是使用第三方Json工具类实现对象的深拷贝,这个可能是最优雅的方式了
        下面举例使用hutools中的JSONUtil工具类实现对象的深拷贝,其他的jackson,fastson,gson等都有类似的方法

        @Data
        public class Person{
            public String pname;
            public int page;
            public Address address;
            public Person() {}
            public Person(String pname,int page,String provices,String city){
                this.pname = pname;
                this.page = page;
                this.address = new Address(provices,city);
            }
        }
        
        @Data
        @AllArgsConstructor
        public class Address {
            private String provices;
            private String city;
        
            @Override
            public String toString() {
                return "Address [provices=" + provices + ", city=" + city + "]";
            }
        }
        
        //Test
        public class Test {
            public static void main(String[] args) {
                Person p = new Person("万一", 24,"浙江","杭州");
                String jsonStr = JSONUtil.toJsonStr(p);
                Person person = JSONUtil.toBean(jsonStr, Person.class);
                person.address.setProvices("江苏");
                person.address.setCity("苏州");
                System.out.println(p==person);
                System.out.println(p);
                System.out.println(person);
                System.out.println(p.address==person.address);
        
            }
        }
        
        //输出结果,可以发现,已经实现对象的深拷贝,两个address对象并不相等
        false
        Person(pname=万一, page=24, address=Address [provices=浙江, city=杭州])
        Person(pname=万一, page=24, address=Address [provices=江苏, city=苏州])
        false
  • 文章推荐