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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...