java深拷貝的實現

来源:https://www.cnblogs.com/yanayo/archive/2019/08/23/javaHome.html
-Advertisement-
Play Games

在有些業務場景下,我們需要兩個完全相同卻彼此無關的java對象。比如使用原型模式、多線程編程等。對此,java提供了深拷貝的概念。通過深度拷貝可以從源對象完美複製出一個相同卻與源對象彼此獨立的目標對象。這裡的相同是指兩個對象的狀態和動作相同,彼此獨立是指改變其中一個對象的狀態不會影響到另外一個對象。 ...


        在有些業務場景下,我們需要兩個完全相同卻彼此無關的java對象。比如使用原型模式、多線程編程等。對此,java提供了深拷貝的概念。通過深度拷貝可以從源對象完美複製出一個相同卻與源對象彼此獨立的目標對象。這裡的相同是指兩個對象的狀態和動作相同,彼此獨立是指改變其中一個對象的狀態不會影響到另外一個對象。實現深拷貝常用的實現方式有2種:Serializable,Cloneable。
        Serializable方式就是通過java對象的序列化和反序列化的操作實現對象拷貝的一種比較常見的方式。本來java對象們都待在虛擬機堆中,通過序列化,將源對象的信息以另外一種形式存放在了堆外。這時源對象的信息就存在了2份,一份在堆內,一份在堆外。然後將堆外的這份信息通過反序列化的方式再放回到堆中,就創建了一個新的對象,也就是目標對象。
--Serializable代碼

public static Object cloneObjBySerialization(Serializable src)
    {
        Object dest = null;
        try
        {
            ByteArrayOutputStream bos = null;
            ObjectOutputStream oos = null;
            try
            {
                bos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(bos);
                oos.writeObject(src);
                oos.flush();
            }
            finally
            {
                oos.close();
            }
            byte[] bytes = bos.toByteArray();
            ObjectInputStream ois = null;
            try
            {
                ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
                dest = ois.readObject();
            }
            finally
            {
                ois.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();//克隆失敗
        }
        return dest;
    }

源對象類型及其成員對象類型需要實現Serializable介面,一個都不能少。

import java.io.Serializable;

public class BattleShip implements Serializable
{
    String name;
    ClonePilot pilot;
    BattleShip(String name, ClonePilot pilot)
    {
        this.name = name;
        this.pilot = pilot;
    }
}
//ClonePilot類型實現了Cloneable介面,不過這對通過Serializable方式拷貝對象沒有影響
public class ClonePilot implements Serializable,Cloneable
{
    String name;
    String sex;
    ClonePilot(String name, String sex)
    {
        this.name = name;
        this.sex = sex;
    }
    public ClonePilot clone()
    {
        try
        {
            ClonePilot dest = (ClonePilot)super.clone();
            return dest;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
}

最後,執行測試代碼,查看結果。

public static void main(String[] args)
{ BattleShip bs
= new BattleShip("Dominix", new ClonePilot("Alex", "male")); System.out.println(bs); System.out.println(bs.name + " "+bs.pilot.name); BattleShip cloneBs = (BattleShip)CloneObjUtils.cloneObjBySerialization(bs); System.out.println(cloneBs); System.out.println(cloneBs.name + " "+cloneBs.pilot.name); }
console--output--

  cloneObject.BattleShip@154617c

  Dominix Alex

  cloneObject.BattleShip@cbcfc0

  Dominix Alex

  cloneObject.ClonePilot@a987ac

  cloneObject.ClonePilot@1184fc6

        從控制台的輸出可以看到,兩個不同的BattleShip對象,各自引用著不同的Clonepilot對象。String作為不可變類,這裡可以作為基本類型處理。該有的數據都有,兩個BattleShip對象也沒有引用同一個成員對象的情況。表示深拷貝成功了。

        註意序列化會忽略transient修飾的變數。所以這種方式不會拷貝transient修飾的變數。

        另外一種方式是Cloneable,核心是Object類的native方法clone()。通過調用clone方法,可以創建出一個當前對象的克隆體,但需要註意的是,這個方法不支持深拷貝。如果對象的成員變數是基礎類型,那妥妥的沒問題。但是對於自定義類型的變數或者集合(集合我還沒測試過)、數組,就有問題了。你會發現源對象和目標對象的自定義類型成員變數是同一個對象,也就是淺拷貝,淺拷貝就是對對象引用(地址)的拷貝。這樣的話源對象和目標對象就不是彼此獨立,而是糾纏不休了。為了彌補clone方法的這個不足。需要我們自己去處理非基本類型成員變數的深拷貝。
--Cloneable代碼

public class Cruiser implements Cloneable
{
    String name;
    ClonePilot pilot;
    Cruiser(String name, ClonePilot pilot)
    {
        this.name = name;
        this.pilot = pilot;
    }

    //Object.clone方法是protected修飾的,無法在外部調用。所以這裡需要重載clone方法,改為public修飾,並且處理成員變數淺拷貝的問題。
    public Cruiser clone()
    {
        try
        {
            Cruiser dest = (Cruiser)super.clone();
            dest.pilot = this.pilot.clone();
            return dest;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
}
public class ClonePilot implements Serializable,Cloneable
{
    String name;
    String sex;
    ClonePilot(String name, String sex)
    {
        this.name = name;
        this.sex = sex;
    }
    //因為所有成員變數都是基本類型,所以只需要調用Object.clone()即可
    public ClonePilot clone()
    {
        try
        {
            ClonePilot dest = (ClonePilot)super.clone();
            return dest;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
}

下麵測試一下

public static void main(String[] args)
{
        Cruiser cruiser = new Cruiser("VNI", new ClonePilot("Alex", "male"));
        System.out.println(cruiser);
        Cruiser cloneCruiser = cruiser.clone();
        System.out.println(cloneCruiser);
        System.out.println(cruiser.pilot);
        System.out.println(cloneCruiser.pilot);
        System.out.println(cruiser.pilot.name);
        System.out.println(cloneCruiser.pilot.name);
}

執行結果如下:

cloneObject.Cruiser@1eba861
cloneObject.Cruiser@1480cf9
cloneObject.ClonePilot@1496d9f
cloneObject.ClonePilot@3279cf
Alex
Alex

        同樣,從控制台的輸出可以看到,兩個不同的Cruiser對象,各自引用著不同的Clonepilot對象。該有的數據都有,兩個Cruiser對象也沒有引用同一個成員對象的情況。表示深拷貝成功了。

        工作中遇到的大多是Serializable方式,這種方式代碼量小,不容易出錯。使用Cloneable方式需要對源對象的數據結構有了足夠的瞭解才可以,代碼量大,涉及的文件也多。雖然他們都需要源對象類型及其引用的成員對象類型實現相應的介面,不過一般情況下問題也不大。但是我曾有幸遇到過一次需要深拷貝的場景,源對象的某個成員變數類型沒有實現任何介面,而且不允許我對此做任何修改。就在我黔驢技窮一籌莫展之際,我看到了光(kryo)。kryo是一個java序列化的框架,特別之處在於他不需要源對象類型實現任何介面,完美的解決了我的問題。後續我會寫一篇kryo框架的使用指南,敬請期待。(絕不咕咕)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 功能需求:在前端頁面中,for迴圈id會構不成連續的順序號,所以要找到一種偽列的方式來根據數據量定義序號 因此就用到了在前端頁面中的一個欄位 forloop.counter,完美解決 ...
  • 集合系列(一):集合框架概述 Java 集合是 Java API 用得最頻繁的一類,掌握 Java 集合的原理以及繼承結構非常有必要。總的來說,Java 容器可以劃分為 4 個部分: List 集合 Set 集合 Queue 集合 Map 集合 除了上面 4 種集合之外,還有一個專門的工具類: 工具 ...
  • 正則表達式: 它是字元串的一種匹配模式,用來處理字元串,可以極大地減輕處理一些複雜字元串的代碼量 字元組:它是在同一位置可能出現的各種字元組成了一個字元組,用[]表示,但是它的結果只能是一個數字或者一個大寫字母或小寫字母等 下麵測試以該網站為例http://tool.chinaz.com/regex ...
  • linearList.h linearList.c index.c ...
  • 中國經典棋盤游戲 華容道,求解最小步數。利用面向對象的分析方法,拆解問題域使其各自可獨立擴展。項目分期計劃:一、實現對橫刀立馬佈局的求解,導出步驟快照。二、實現對標準華容道滑塊(卒、五虎上將、曹操)任意佈局的求解。三、實現對任意自定義滑塊、自定義棋盤的求解。 ...
  • Thread是學習我們學習多線程接觸到的第一個有關多線程的類,相信每一個學習過或者瞭解過Java多線程的小伙伴都知道Thread類。這次分享主要對Thread的start方法進行講解。 相信大家都知道,start方法是啟動一個線程,並且該線程進入了可執行狀態。在實際的編碼中,我們是重寫run()方法 ...
  • 本篇概述 Django Admin後臺顯示 多對多欄位(如何) Django 模板 顯示 多對多欄位(如何) models代碼背景 一,Django Admin後臺顯示 多對多欄位 於admin.py文件中 二,Django 模板 顯示 多對多欄位 先與 視圖 views.py文件中 然後,(url ...
  • 1、文件上傳簡單流程分析圖: 2、Fastdfs介紹: Fastdfs由兩個角色組成: Tracker(集群):調度(幫你找到有空閑的Storage) Storage(集群):文件存儲(幫你保存文件或獲取需要的文件) 流程: 1.Storage和tracker 發送心跳連接。 2.客戶端請求trac ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...