日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

java clone() 方法详解及深克隆与浅克隆

發布時間:2024/1/1 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java clone() 方法详解及深克隆与浅克隆 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概述

clone 翻譯過來就是 克隆,顧名思義就是創造一個一模一樣的事物。Java 代碼中 clone() 方法是 Object 方法,而 Object 又是所有類的父類,也就是說所有 java 對象都可以調用 clone 方法克隆一個和自己 “相同” 的對象。本篇博客我打算系統整理 java clone() 方法的原理以及深克隆和淺克隆的關系。


cloneable 接口

在正式介紹 clone() 方法前,我們先簡單聊聊 cloneable 接口。在編碼過程中,一般都是通過實現該接口重寫 clone() 方法,然后就可以調用 clone() 方法執行重寫后的邏輯。

需要注意的一點是:Object 類中的 clone() 方法是通過 native 修飾的,也就是說它是通過直接調用底層操作系統方法實現的,默認不重寫 clone() 方法時調用 Object 類的 clone() 方法。


clone()

java 對象調用 clone() 方法可以創建一個新的對象。除了調用 clone() 方法外,java 語言還有很多種創建對象的方式,本篇我們主要看 new 關鍵字clone() 方法

  • new :當我們使用 new 關鍵字創建對象時,jvm 首先根據關鍵字后面的類型確定需要申請的內存大小,申請完內存后,執行類的構造方法。在執行構造方法期間,填充內存中各個屬性域,這個填充的過程也叫 初始化。構造方法執行完標志著對象創建成功,此時返回對象地址,在棧區以引用的方式調用對象。
  • clone :當我們調用某個對象的 clone() 方法克隆對象時,首先根據原對象的內存大小申請內存空間,申請完內存空間后,將原對象內存域復制到新申請的內存空間,復制完成標志著克隆完成,返回引用類型。

通過上面的描述我們可以看出,new 和 clone() 第一步都是申請內存,只不過 new 關鍵字通過類構造方法初始化對象,clone() 方法直接通過克隆內存域完成對象創建。


對象和引用

在正式開始介紹 clone() 方法前,我們先簡單描述對象和引用的區別:

  • 對象:絕大多數對象在堆區,它是實際保存屬性的內存空間
  • 引用:引用大多引用在棧區,可以將它理解為指向實際對象地址的指針

關于對象和引用我簡單總結如下:

  • 一個對象可以有多個引用,但一個引用只能指向一個對象
  • 當我們使用 == 比較對象時,一般比較的是對象地址

舉個簡單的例子:

@Test public void test1() {ClassRoom c1 = new ClassRoom();ClassRoom c2 = c1;System.out.println(c1);System.out.println(c2);System.out.println(c1 == c2); }

執行結果

com.qhd.worker.CloneTest$ClassRoom@927a81 com.qhd.worker.CloneTest$ClassRoom@927a81 true

從執行結果可以看出,即使是兩個不同的引用,指向相同對象時,他們也是相同的,并且值都是指向對象的地址。簡單繪圖表示的話,如下所示:

有了上面的基礎,我們再來看 clone() 方法克隆:

@Test public void test2() throws CloneNotSupportedException {ClassRoom c1 = new ClassRoom();ClassRoom c2 = (ClassRoom) c1.clone();System.out.println(c1);System.out.println(c2);System.out.println(c1 == c2); }

執行結果

com.qhd.worker.clone.CloneTest$ClassRoom@927a81 com.qhd.worker.clone.CloneTest$ClassRoom@e03bb5 false

從執行結果可以看出,此時兩個引用指向不同地址。clone() 方法創建了一個新的對象:


深克隆與淺克隆

通過上面的整理可以看出,clone() 方法會創建一個新的對象。下面我們主要看兩種克隆的關系:

  • 如果一個對象中所有屬性都是基礎類型,那么它的深克隆和淺克隆結果完全相同
  • 如果一個對象包含引用類型數據,如果克隆之后的引用所指向對象是不同對象,那么它是深克隆,否則是淺克隆

也就是說:深克隆、淺克隆的劃分完全是根據是否克隆引用屬性來說的。對于基本類型變量,克隆后數值大小相同即可,但對于引用數據類型,深克隆會復制引用指向的對象,而淺克隆只會克隆引用。

舉個例子,假設現在存在如下 class 類:

class ClassRoom implements Cloneable {// 班級人數private int num;// 班級名稱private String name;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();} }

下面我通過抽象圖的形式描述深克隆和淺克隆的區別:

通過上圖可以明顯看出,深克隆時 String 引用對應會被克隆,但淺克隆只會克隆引用,不會克隆具體對象。下面我通過簡單的代碼測試 clone() 方法默認是深克隆還是淺克隆。

@Test public void test2() throws CloneNotSupportedException {ClassRoom c1 = new ClassRoom(1, "三年二班");ClassRoom c2 = (ClassRoom) c1.clone();System.out.println(c1.name);System.out.println(c2.name);System.out.println(c1.name == c2.name); }

執行結果

三年二班 三年二班 true

從輸出結果可以看出,c1對象和c2對象的 name 引用屬性指向同一字符串對象。也就是說,此時 clone() 方法是淺克隆。


淺克隆改深克隆

在實際應用開發中,淺克隆肯定不能滿足所有業務場景。部分情況下,需要將淺克隆優化為深克隆,具體實現方法也很簡單:實現 Cloneable 接口,重寫 clone() 方法,在 clone() 方法中手動克隆引用屬性。下面我們具體看代碼:

class Teacher implements Cloneable {int age;String name;public Teacher(int age, String name) {this.age = age;this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();} }class SchoolRoom implements Cloneable {int num;Teacher teacher;public SchoolRoom(int num, Teacher teacher) {this.num = num;this.teacher = teacher;}@Overrideprotected Object clone() throws CloneNotSupportedException {SchoolRoom result = (SchoolRoom) super.clone();result.teacher = (Teacher) this.teacher.clone();return result;} }@Test public void test3() throws CloneNotSupportedException {Teacher teacher = new Teacher(28, "張三");SchoolRoom schoolRoom = new SchoolRoom(40, teacher);SchoolRoom schoolRoom2 = (SchoolRoom) schoolRoom.clone();System.out.println(schoolRoom == schoolRoom2);System.out.println(schoolRoom.teacher == schoolRoom2.teacher); }

執行結果

false false

從執行結果可以看出,此時對于 SchoolRoom 類,無論是常量屬性還是引用類型屬性,clone() 方法前后都不相同。也就是說克隆前后,引用屬性指向不同的對象,clone() 方法為深克隆。


深入探討深克隆

前面我們提到,深克隆是指除基礎類型外,引用類型克隆前后指向不同對象。那么克隆對象引用屬性的引用屬性需要指向不同對象嗎。下面我們看代碼:

@Test public void test4() throws CloneNotSupportedException {Teacher teacher = new Teacher(28, "張三");SchoolRoom schoolRoom = new SchoolRoom(40, teacher);SchoolRoom schoolRoom2 = (SchoolRoom) schoolRoom.clone();System.out.println(schoolRoom.teacher.name == schoolRoom2.teacher.name); }

執行結果

true

從執行結果可以看出,此時雖然 SchoolRoom 的 teacher 引用屬性克隆前后指向對象不同,但 teacher 對象的 name 引用屬性還是指向同一字符串。用抽象圖表示如下:

從上圖也就可以看出,實際上此時克隆還不是完全意義上的深克隆,因為 SchoolRoom 對象 teacher 引用的 name 屬性克隆前后還指向相同對象。我們可以稱這種克隆為 不徹底的深克隆

想要做到完完整整的深克隆,必須保證所有引用屬性克隆后都會創建新對象,并且這個過程需要無限向下遞歸,直到只剩下常量屬性。想要實現這種程度的深克隆幾乎是不可能的,一旦代碼中引入 SDK 包中的類,該類沒有重寫 clone() 方法,就無法實現。


Object clone() 方法

最后,我們再看看 Object 類的 clone() 方法,具體源碼如下:

protected native Object clone() throws CloneNotSupportedException;

從源碼可以看出,該方法是 protected 修飾的 native 方法。下面我們看一段代碼:

class A{ }@Test public void test5() throws CloneNotSupportedException {A a = new A();A a2 = (A)a.clone(); // mark }

這段代碼我標記的那一行會報編譯錯誤:

'clone()' has protected access in 'java.lang.Object'

具體原因是由于:雖然 Object 類是所有類的父類,并且 clone() 方法是 protected 修飾的方法。當子類和父類不在同一個包時,子類只能訪問自身從父類繼承而來的受保護的成員,而不能訪問父類實例本身受保護的成員。

在上述案例中,我們把代碼改成如下所示就不會報錯了:

class A implements Cloneable{@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();} }@Test public void test5() throws CloneNotSupportedException {A a = new A();A a2 = (A)a.clone(); }

關于其中的原理后面我們整理java權限相關知識時再細說。


參考:
https://blog.csdn.net/zhangjg_blog/article/details/18369201
https://blog.csdn.net/qq_37113604/article/details/81168224
https://blog.csdn.net/qq_38962004/article/details/79720416

總結

以上是生活随笔為你收集整理的java clone() 方法详解及深克隆与浅克隆的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。