Java提高篇 —— Java淺拷貝和深拷貝

一、前言

?

? ? ? ?我們知道在Java中存在這個接口Cloneable,實現該接口的類都會具備被拷貝的能力,同時拷貝是在內存中進行,在性能方面比我們直接通過new生成對象來的快,特別是在大對象的生成上,使得性能的提升非常明顯。然而我們知道拷貝分為深拷貝和淺拷貝之分,但是淺拷貝存在對象屬性拷貝不徹底問題。下面我們就具體來看一下深淺拷貝問題。

?

二、定義

?

首先來看看淺拷貝和深拷貝的定義:

? ? ? ?淺拷貝:使用一個已知實例對新創建實例的成員變量逐個賦值,這個方式被稱為淺拷貝。

? ? ? ?深拷貝:當一個類的拷貝構造方法,不僅要復制對象的所有非引用成員變量值,還要為引用類型的成員變量創建新的實例,并且初始化為形式參數實例值。這個方式稱為深拷貝。

? ? ? ?也就是說淺拷貝只復制一個對象,傳遞引用,不能復制實例。而深拷貝對對象內部的引用均復制,它是創建一個新的實例,并且復制實例。

? ? ? ?對于淺拷貝當對象的成員變量是基本數據類型時,兩個對象的成員變量已有存儲空間,賦值運算傳遞值,所以淺拷貝能夠復制實例。但是當對象的成員變量是引用數據類型時,就不能實現對象的復制了。

?

三、淺拷貝

?

我們先看如下代碼:

public class Person implements Cloneable{/** 姓名 **/private String name;/** 電子郵件 **/private Email email;public String getName() {return name;}public void setName(String name) {this.name = name;}public Email getEmail() {return email;}public void setEmail(Email email) {this.email = email;}public Person(String name,Email email){this.name  = name;this.email = email;}public Person(String name){this.name = name;}protected Person clone() {Person person = null;try {person = (Person) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return person;}
}public class Client {public static void main(String[] args) {//寫封郵件Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議...");Person person1 =  new Person("張三",email);Person person2 =  person1.clone();person2.setName("李四");Person person3 =  person1.clone();person3.setName("王五");System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent());System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent());System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent());}
}

輸出結果:

張三的郵件內容是:請與今天12:30到二會議室參加會議...
李四的郵件內容是:請與今天12:30到二會議室參加會議...
王五的郵件內容是:請與今天12:30到二會議室參加會議...

? ? ? ?在該應用程序中,首先定義一封郵件,然后將該郵件發給張三、李四、王五三個人,由于他們是使用相同的郵件,并且僅有名字不同,所以使用張三該對象類拷貝李四、王五對象然后更改下名字即可。程序一直到這里都沒有錯,但是如果我們需要張三提前30分鐘到,即把郵件的內容修改下:

public class Client {public static void main(String[] args) {//寫封郵件Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議...");Person person1 =  new Person("張三",email);Person person2 =  person1.clone();person2.setName("李四");Person person3 =  person1.clone();person3.setName("王五");person1.getEmail().setContent("請與今天12:00到二會議室參加會議...");System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent());System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent());System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent());}
}

? ? ? ?在這里同樣是使用張三該對象實現對李四、王五拷貝,最后將張三的郵件內容改變為:請與今天12:00到二會議室參加會議...。但是結果是:

張三的郵件內容是:請與今天12:00到二會議室參加會議...
李四的郵件內容是:請與今天12:00到二會議室參加會議...
王五的郵件內容是:請與今天12:00到二會議室參加會議...

? ? ? ?這里我們就疑惑了為什么李四和王五的郵件內容也發送了改變呢?讓他們提前30分鐘到人家會有意見的!

? ? ? ?其實出現問題的關鍵就在于clone()方法上,我們知道該clone()方法是使用Object類的clone()方法,但是該方法存在一個缺陷,它并不會將對象的所有屬性全部拷貝過來,而是有選擇性的拷貝,基本規則如下:

? ? ? ?1、 基本類型:如果變量是基本很類型,則拷貝其值,比如int、float等。

? ? ? ?2、 對象:如果變量是一個實例對象,則拷貝其地址引用,也就是說此時新對象與原來對象是公用該實例變量。

? ? ? ?3、 String字符串:若變量為String字符串,則拷貝其地址引用。但是在修改時,它會從字符串池中重新生成一個新的字符串,原有紫都城對象保持不變。

?

四、深拷貝

?

? ? ? ?基于上面上面的規則,我們很容易發現問題的所在,他們三者公用一個對象,張三修改了該郵件內容,則李四和王五也會修改,所以才會出現上面的情況。對于這種情況我們還是可以解決的,只需要在clone()方法里面新建一個對象,然后張三引用該對象即可:

protected Person clone() {Person person = null;try {person = (Person) super.clone();person.setEmail(new Email(person.getEmail().getObject(),person.getEmail().getContent()));} catch (CloneNotSupportedException e) {e.printStackTrace();}return person;
}

? ? ? ?所以:淺拷貝只是Java提供的一種簡單的拷貝機制,不便于直接使用。

? ? ? ?對于上面的解決方案還是存在一個問題,若我們系統中存在大量的對象是通過拷貝生成的,如果我們每一個類都寫一個clone()方法,并將還需要進行深拷貝,新建大量的對象,這個工程是非常大的,這里我們可以利用序列化來實現對象的拷貝。

?

五、利用序列化實現對象的深拷貝

?

? ? ? ?如何利用序列化來完成對象的拷貝呢?在內存中通過字節流的拷貝是比較容易實現的。把母對象寫入到一個字節流中,再從字節流中將其讀出來,這樣就可以創建一個新的對象了,并且該新對象與母對象之間并不存在引用共享的問題,真正實現對象的深拷貝。

public class CloneUtils {@SuppressWarnings("unchecked")public static <T extends Serializable> T clone(T obj){T cloneObj = null;try {//寫入字節流ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream obs = new ObjectOutputStream(out);obs.writeObject(obj);obs.close();//分配內存,寫入原始對象,生成新對象ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());ObjectInputStream ois = new ObjectInputStream(ios);//返回生成的新對象cloneObj = (T) ois.readObject();ois.close();} catch (Exception e) {e.printStackTrace();}return cloneObj;}
}

? ? ? ?使用該工具類的對象必須要實現Serializable接口,否則是沒有辦法實現克隆的。

public class Person implements Serializable{private static final long serialVersionUID = 2631590509760908280L;..................//去除clone()方法}public class Email implements Serializable{private static final long serialVersionUID = 1267293988171991494L;....................
}

? ? ? ?所以使用該工具類的對象只要實現Serializable接口就可實現對象的克隆,無須繼承Cloneable接口實現clone()方法。

public class Client {public static void main(String[] args) {//寫封郵件Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議...");Person person1 =  new Person("張三",email);Person person2 =  CloneUtils.clone(person1);person2.setName("李四");Person person3 =  CloneUtils.clone(person1);person3.setName("王五");person1.getEmail().setContent("請與今天12:00到二會議室參加會議...");System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent());System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent());System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent());}
}

輸出結果:

張三的郵件內容是:請與今天12:00到二會議室參加會議...
李四的郵件內容是:請與今天12:30到二會議室參加會議...
王五的郵件內容是:請與今天12:30到二會議室參加會議...

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446952.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446952.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446952.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

openssl里面AES算法主要函數的參數的介紹

注意事項 使用API的時候&#xff0c;需要特別小心數據長度&#xff0c;一般沒有指定長度的參數&#xff0c;默認都是16&#xff08;AES_BLOCK_SIZE&#xff09;個字節。輸出數據的長度一般都是16字節的倍數&#xff0c;否則會出現數組越界訪問。以下API中&#xff0c;encrypt表…

Java提高篇 —— Java內部類詳解

一、簡介 內部類是一個非常有用的特性但又比較難理解使用的特性。 內部類我們從外面看是非常容易理解的&#xff0c;無非就是在一個類的內部在定義一個類。 public class OuterClass {private String name ;private int age;public String getName() {return name;}public voi…

Ubuntu修改界面的大小

命令 xrandr 就會顯示ubuntu支持的屏幕比例使用命令 xrandr --size 1680x1050 切換屏幕大小

Java提高篇 —— String緩沖池

一、String緩沖池 首先我們要明確&#xff0c;String并不是基本數據類型&#xff0c;而是一個對象&#xff0c;并且是不可變的對象。查看源碼就會發現String類為final型的&#xff08;當然也不可被繼承&#xff09;&#xff0c;而且通過查看JDK文檔會發現幾乎每一個修改String對…

C++最新使用開源openssl實現輸入是文件,輸出是文件的AES加解密的代碼

AES.h頭文件 #include <cstring> #include <fstream> #include <iostream> #include <openssl/aes.h>//AES文件加密函數 int aes_encrypt_file(const std::string &original_backup_file_path,const std::string &encrypted_file_path,const …

Java基礎 —— JVM內存模型與垃圾回收

目錄一、概述二、運行時數據區方法區運行時常量池堆棧本地方法棧程序計數器三、對象訪問四、垃圾回收如何定義垃圾1、引用計數法2、可達性分析垃圾回收方法1、Mark-Sweep標記-清除算法2、Copying復制算法3、Mark-Compact標記-整理算法4、Generational Collection 分代收集垃圾收…

Report Design

ERP_ENT_STD-CSDN博客

規范化流程化提交自己代碼到遠程gitlab服務器

流程 進入到build目錄使用make命令進行編譯 make -j 6&#xff0c;前提是cmake命令已經執行../format.py rungit add .. 添加文件git checkout -b xxx 創建xxx分支&#xff0c;其中xxx是分支名字git branch 查看分支git commit -m "[chy/backup] modify parameters for…

Java提高篇 ——Java注解

目錄一、注解注解的定義注解的應用元注解RetentionDocumentedTargetInheritedRepeatable注解的屬性Java 預置的注解DeprecatedOverrideSuppressWarningsSafeVarargsFunctionalInterface二、注解的提取三、注解與反射四、注解的使用場景五、親手自定義注解完成某個目的六、注解應…

linux使用openssl查看文件的md5數值

代碼 #include <stdio.h> #include <openssl/md5.h>std::string get_file_md5(const char *path){unsigned char digest [MD5_DIGEST_LENGTH];std::ifstream file(path, std::ios::in | std::ios::binary); //打開文件MD5_CTX md5_ctx;MD5_Init(&md5_ctx);cha…

Android 性能優化四個方面總結

目錄一、四個方面二、卡頓優化1、Android系統顯示原理2、卡頓根本原因3、性能分析工具&#xff08;1&#xff09;Profile GPU Rendering&#xff08;2&#xff09;TraceView&#xff08;3&#xff09;Systrace UI 性能分析4、優化建議&#xff08;1&#xff09;布局優化&#x…

pycharm/clion/idea等產品多含代碼左移右移操作

左移 選中多行代碼后&#xff0c;按下Tab鍵&#xff0c;一次縮進四個字符 右移 鼠標選中多行代碼后&#xff0c;同時按住shiftTab鍵&#xff0c;一次左移四個字符

Android 開源框架選擇

目錄一、前言二、APP的整體架構三、技術選型的考量點四、日志記錄能力五、JSON解析能力1、gson2、jackson3、Fastjson4、LoganSquare六、數據庫操作能力1、ActiveAndroid2、ormlite3、greenDAO4、Realm七、網絡通信能力1、android-async-http2、OkHttp3、Volley4、Retrofit八、…

使用opensll的md5對于string進行加密

代碼 #include <openssl/md5.h>#include <sstream> #include <iomanip> #include <iostream>void get_string_md5(const std::string& await_md5_string) {unsigned char md5[MD5_DIGEST_LENGTH];MD5(reinterpret_cast<unsigned const char*&g…

Android Studio 自定義Gradle Plugin

一、簡介 之前公司的一個項目需要用到Gradle插件來修改編譯后的class文件&#xff0c;今天有時間就拿出來整理一下&#xff0c;學習一下Gradle插件的編寫還是一件十分有意義的事。 二、Gradle插件類型 一種是直接在項目中的gradle文件里編寫&#xff0c;這種方式的缺點是無法復…

C++創建臨時文件

命令 std::string original_backup_file std::tmpnam(nullptr);

Android Studio Gradle兩種更新方式

第一種、Android Studio自動更新 第一步&#xff1a;修改gradle版本 修改項目根目錄/gradle/wrapper/gradle-wrapper.properties最后一行的地址&#xff1a; distributionUrlhttps://services.gradle.org/distributions/gradle-3.3-all.zip新gradle地址從官方下載的地方有。…

C++使用openssl實現aes加解密,其中加密是string到文件,解密是文件到string,切合項目背景

代碼 使用md5對于用戶輸入的密碼進行保護,也使得密碼的長度固定crypto_util.h#pragma once#include <string>namespace hsm{ namespace mgmt{void get_md5_digest(const std::string &data,uint8_t result[16]);void aes_encrypt_to_file(const std::string &fi…

Android Canvas的drawText()和文字居中方案

自定義View是繪制文本有三類方法&#xff1a; // 第一類 public void drawText (String text, float x, float y, Paint paint) public void drawText (String text, int start, int end, float x, float y, Paint paint) public void drawText (CharSequence text, int start…

IntelliJ IDEA配置Tomcat

查找該問題的童鞋我相信IntelliJ IDEA&#xff0c;Tomcat的下載&#xff0c;JDK等其他的配置都應該完成了&#xff0c;那我直接進入正題了。 1、新建一個項目 2、由于這里我們僅僅為了展示如何成功部署Tomcat&#xff0c;以及配置完成后成功運行一個jsp文件&#xff0c;我僅勾…