Java的淺拷貝與深拷貝

Java的淺拷貝與深拷貝

一、概念

  • 淺拷貝
    • 淺拷貝僅僅復制所考慮的對象(包括對象中的基本變量),而不復制它所引用的對象。
  • 深拷貝
    • 深拷貝把要復制的對象所引用的對象都復制了一遍,并拷貝屬性指向的動態(tài)分配的內存。
  • 簡單描述一下,就是淺拷貝引用了同一個對象地址,而深拷貝卻是一個新的對象。

二、問題來源

  • 在一個web請求接口中,返回對象多次封裝了同一個結合,導致返回json中的內容體出現(xiàn)了$ref。

  • 問題重現(xiàn):

    • 定義一個Student(String name,Age age),Age(Integer age)類

      public static void main(String[] args) {
          Student student1 = new Student("張三",new Age(14));
          Student student2 = new Student("李四",new Age(15));
          List son = Arrays.asList(student1,student2);
          List<List> father = new ArrayList<>();
          father.add(son);
          father.add(son);
          System.out.println(JSON.toJSONString(father));
          System.out.println(JSON.toJSONString(father, SerializerFeature.DisableCircularReferenceDetect));
      }
      //[[{"age":14,"name":"張三"},{"age":15,"name":"李四"}],{"$ref":"$[0]"}]
      //[[{"age":14,"name":"張三"},{"age":15,"name":"李四"}],[{"age":14,"name":"張三"},{"age":15,"name":"李四"}]]
      
  • FastJson循環(huán)引用問題

    • 解決方案一:

      • 簡單轉換

        System.out.println(JSON.toJSONString(father, SerializerFeature.DisableCircularReferenceDetect));
        
      • 配置SpringBoot項目中的json序列化配置

        fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
        
    • 解決方案二:

      • 創(chuàng)建新對象,不循環(huán)引用。
  • 當然,最好的解決辦法是創(chuàng)建對象,F(xiàn)astJson提供了一個監(jiān)測機制,防止循環(huán)解析導致StackOverflowError。某一次的問題可以判斷為不是循環(huán)引用,但無法保證下一次的循環(huán)引用。把監(jiān)測關了,下一次就可能出現(xiàn)StackOverflowError。避免這個問題,就要創(chuàng)建新的對象。于是就來到了數(shù)據(jù)的拷貝。

三、拷貝

先定義一個基本對象

public class CopyObject implements Cloneable{
    // 基本類型
    private String name;
    // 引用類型
    private StringBuilder add;

    public CopyObject(String name, StringBuilder add) {
        this.name = name;
        this.add = add;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public StringBuilder getAdd() {
        return add;
    }

    public void setAdd(StringBuilder add) {
        this.add = add;
    }

    // 淺拷貝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

3.1 淺拷貝

  • 復制基本數(shù)據(jù)類型

  • 引用老的引用數(shù)據(jù)類型

    Student、Age類實現(xiàn)CloneAble接口,重寫實現(xiàn)clone() 
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
    
    public static void main(String[] args) throws CloneNotSupportedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        Age age = new Age(13);
        Student source = new Student("a", age);
        Student target = (Student) source.clone();
        Student target =  SerializationUtils.clone(source);
        Student target = JSONObject.parseObject(JSONObject.toJSONString(source), Student.class);
        System.out.println("step1 =====================");
        System.out.println("source:" + source.getName() + "  " + source.getAge().getAge());
        //source:a  13
        System.out.println("target:" + target.getName() + "  " + target.getAge().getAge());
        target.setName("b");
        //target:a  13
        target.getAge().setAge(15);
        System.out.println("step2 =====================");
        System.out.println("source:" + source.getName() + "  " + source.getAge().getAge());
        //source:a  15
        System.out.println("target:" + target.getName() + "  " + target.getAge().getAge());
        //target:a  15
    }
    
  • 圖解:

    ShallowCopy

3.1 深拷貝

  • 復制所有的的引用數(shù)據(jù)類型

    重寫Student的實現(xiàn)clone() 
        @Override
        protected Student clone() throws CloneNotSupportedException {
            Student student = (Student) super.clone();
            student.setAge((Age) age.clone());
            return student;
        }
    
    public static void main(String[] args) throws CloneNotSupportedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        Age age = new Age(13);
        Student source = new Student("a", age);
        //        Student target = (Student) source.clone();
        //        Student target =  SerializationUtils.clone(source);
        Student target = JSONObject.parseObject(JSONObject.toJSONString(source), Student.class);
        System.out.println("step1 =====================");
        System.out.println("source:" + source.getName() + "  " + source.getAge().getAge());
        //source:a  13
        System.out.println("target:" + target.getName() + "  " + target.getAge().getAge());
        //target:a  13
        target.setName("b");
        target.getAge().setAge(15);
        System.out.println("step2 =====================");
        System.out.println("source:" + source.getName() + "  " + source.getAge().getAge());
        //source:a  13
        System.out.println("target:" + target.getName() + "  " + target.getAge().getAge());
        //target:b  15
    }
    
  • 圖解:

    DeepCopy

四、實現(xiàn)

  • 淺拷貝

    • 默認Object就帶有clone方法,但是屬于protect。
    • 需要實現(xiàn)Cloneable工具類之后,重現(xiàn)clone方法。
    • 也有許多其他的工具類提供序列化,使用的時候一定要注意原理,再使用。
  • 深拷貝

    • 序列化與反序列化的機制存在下,會保證對象完成不一樣。
    1. 利用common-lang序列化工具clone工具

      // Student、Age需要實現(xiàn)Serializable
       Student targetShallow =  SerializationUtils.clone(source);
      
      
    2. 對于復雜的數(shù)組對象無法直接序列化,也可使用JSON序列化之后,再反序列化

       Student target = JSONObject.parseObject(JSONObject.toJSONString(source), Student.class);
      
      
  • 【強制】避免用 Apache Beanutils 進行屬性的 copy。(阿里巴巴Java開發(fā)手冊)

    • Apache BeanUtils 性能較差,可以使用其他方案比如 Spring BeanUtils, Cglib BeanCopier,注意 均是淺拷貝。

本篇文章由一文多發(fā)平臺ArtiPub自動發(fā)布

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容