Overview of Content
如有引用參考請詳註出處,感謝
在這篇文章中,我們將深入研究複製(Clone Pattern
)的應用場景
首先,我們會提供整體內容的概述,並進入複製的使用場景。接著,我們將解釋複製的定義,並透過UML結構呈現其模型。在深入研究時,我們會探討複製的特色,並特別注意其中的注意事項。同時,我們會分析複製技術的優缺點,使讀者對其應用有更清晰的認識。
在實際實現部分,我們將深入探討淺拷貝和深拷貝的區別,並以 Java 中的 LinkedList、ArrayList 以及 Arrays.copyOf 方法為例進行淺拷貝的實踐
最後,我們將探討在 Android 源碼中的 Intent 傳遞中複製的應用場景。這將有助於讀者更全面地理解複製的實際應用。
Clone 使用場景
原型模式 多是使用在,物件複雜初始化的地方,複製一個已經存在的實例,可使效率提高
● 在類別初始化時消耗過多的資源,原型拷貝可降低消耗量
● 透過 new 出的一個物件,需要反鎖的過程
以下創立物件的過程
- 檢查 Classloader:加載是否已經加載該 Class
- 分配記憶體內存:避免 Mutli Thread 不安全的使用記憶體
- 內存初始化:清理舊資料
- 設定 - 對象頭資訊
- 對象初始化:初始化 static Field、呼叫建後函數
● 一個物件需要給其他類別修改值時,最終才以一個結果決定值時,可以拷貝多個物件給需要的類,這可稱為 保護性拷貝
● 要用 Clone 還是 new 來創建對象 ?
Clone 並不依定比 new 速度還要快(一般來說是會比 new 快),需要使用者自己考量物件的大小、方便性、使用場景... 等等
Clone 是透過記憶體二進制流(
Binary stream
)拷貝
Clone 定義 & Clone UML
● Clone 定義
用原型實例指定創建對象的種類,並且通過 拷貝原型對象取得新對象
● Clone UML 模型
角色 | 功能 |
---|---|
Cloneable | 標示介面,這種介面內部通常沒有方法,是為了標示該類「擁有」某種特性 |
ConcreateClone | 實作 Cloneable 介面的類,也就是真正要拷貝時會調用到的類 |
Clone 特色 - 注意事項
A. 不會呼叫建構函數:
使用 clone
方法 不會呼叫建構函數
B. 預設淺拷貝:clone
並 不會複製內部已有的物件(如 List
、Map
... 等等其他物件),所以要 手動 針對內部有的物件進行操作 (深拷貝)
淺拷貝只會拷貝,基礎的 8 大類別、String 類... 其他有關 數組、引用對象 是不會拷貝的
● 在 Java 中 Cloneable 是一個 標示接口,這個接口內並沒有任何方法
// Cloneable.java
public interface Cloneable {
}
所謂的 標示,代表該接口是用來 判斷,像是所有類都隱式繼承 Object 類,Object 內就有一個 clone 方法,在 clone 方法內就有判斷該接口
// Object.java
protected Object clone() throws CloneNotSupportedException {
// 判斷該類是否有 Cloneable 接口
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
// 透過 Native 複製物件
return internalClone();
}
/*
* Native helper method for cloning.
*/
@FastNative
private native Object internalClone();
Clone - 優缺點
● 拷貝物件不一定比 new 物件速度更快,需要多加考量,原型模式是在 記憶體
中二進制串流(Binary Stream
)的拷貝,比起 new 可少掉許多步驟
優點 | 缺點 |
---|---|
唯讀物件可以使用,防止物件被修改 | 它不進行 new 的流程,也就不呼叫建構函數 |
特別是在一個需要產生大量物件的地方,可以看出 clone 的好處 | 優點是少了約束,缺點也是少了約束 |
Clone 實現:淺拷貝 & 深拷貝
如果有寫過 C++,就會對 C++ 深淺拷貝 比較有印象,因為必須覆寫 指定操作符、複製建構函數 這兩個方法
ShallowClone 淺拷貝
● 預設物件的 clone 是淺拷貝,我們先來寫一個簡單的範例
public class ShallowClone implements Cloneable {
String name;
String describe;
@Override
public ShallowClone clone() {
System.out.println("ShallowClone Use clone");
try {
// clone 是淺拷貝
return (ShallowClone) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
ShallowClone() {
//"1. " 原先建構函數
System.out.println("Make ShallowClone Store");
name = "Java";
describe = "Basic";
}
public void print() {
System.out.println(
"This hashCode: " + hashCode() +
"\nName: " + name + ", " + name.hashCode() +
"\nDescribe:" + describe + ", " + describe.hashCode());
}
}
class ShallowMain {
public static void main(String[] args) {
System.out.println("\nOriginal----------------Create original Object:");
ShallowClone original = new ShallowClone();
original.print();
System.out.println("\nClone----------------Start clone:");
ShallowClone clone = original.clone();
clone.print();
System.out.println("\nClone----------------Change clone member:");
original.name = "Android";
original.describe = "OOP";
clone.print();
System.out.println("\nOriginal----------------After clone change, original Object:");
original.print();
}
}
--實作--
A. 產生不同物件:透過 Object#clone 函數,確實自身可以產生不同的物件 (透過 hashCode 觀察)、其 member 也可以
● 請注意!透過 Object#clone 並 不會觸發建構函數
constructor
B. 複製原物建 member value:Clone 出來的物件,其 member 會的 Value 都與原來物件指向的位置相同 (這樣也滿符合 clone 的意思)
C. 自己手動設定 Clone 物件的成員,會發現 原本指向元物件的位置會改變,變成全新的物件位置
● 這有點類似於 Linux 的 Copy on Write 技術
DeepClone 深拷貝
● 深拷貝就是我們要手動處理當前物件 member 的 clone 行為,新增一個 ArrayList
進行拷貝
A. 首先先來看看,未處理引用 member 時,就拷貝的狀況
public class DeepClone implements Cloneable {
String name;
String describe;
List<Integer> number = new ArrayList<>();
@Override
public DeepClone clone() {
System.out.println("DeepClone Use clone");
try {
return (DeepClone) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
DeepClone() {
//"1. " 原先建構函數
System.out.println("Make DeepClone Store");
name = "Java";
describe = "Basic";
number.add(1111);
}
public void print() {
System.out.println(
"This hashCode: " + hashCode() +
"\nName: " + name + ", " + name.hashCode() +
"\nDescribe: " + describe + ", " + describe.hashCode() +
"\nNumber: " + number.toString()
);
}
}
class DeepMain {
public static void main(String[] args) {
System.out.println("\nOriginal----------------Create original Object:");
DeepClone original = new DeepClone();
original.print();
System.out.println("\nClone----------------Start clone:");
DeepClone clone = original.clone();
clone.print();
System.out.println("\nClone----------------Change clone member:");
clone.name = "Android";
clone.describe = "OOP";
clone.number.add(9999);
clone.print();
System.out.println("\nOriginal----------------After clone change, original Object:");
original.print();
}
}
從結果可以看出一個重要問題,LinkedList 這個 member 被重複使用。在 clone 後,添加 List 會 影響到原先的物件
--實作--
B. 解決方式:自己手動處理 clone 方法,解決每個 member 的 clone 問題
@Override
public DeepClone clone() {
System.out.println("DeepClone Use clone");
try {
DeepClone clone = (DeepClone) super.clone();
clone.name = clone.describe = "";
clone.number = new ArrayList<>(this.number);
return clone;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
--實作--
Java List 列表
LinkedList 淺拷貝
● Java LinkedList 類,透過自己處理 clone 方法來達成拷貝,我們現在來看看它的 Clone 方式
public class LinkedListClone implements Cloneable {
private String name;
private int age;
private final LinkedList<String> event = new LinkedList<>();
@Override
public LinkedListClone clone() {
System.out.println("\n\n LinkedListClone Use clone");
try {
// clone 是淺拷貝
return (LinkedListClone) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
LinkedListClone() {
//"1. " 原先建構函數
System.out.println("Make LinkedListClone Store");
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setEvent(String... mem) {
for(String m:mem) {
if(!event.contains(m)) {
event.add(m);
}
}
}
public void print() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
for(String m: event) {
System.out.print(m + "... ");
}
System.out.println();
}
}
class LinkedListMain {
public static void main(String[] args) {
LinkedListClone i = new LinkedListClone();
i.setName("Pan");
i.setAge(13);
i.setEvent("Hello", "World");
i.print();
LinkedListClone ii = i.clone();
ii.setAge(16);
ii.setEvent("Test", "1234");
ii.print();
}
--實作--
● LinkedList 在 clone 後:將原來 List 的內容,添加到新的 List 中 (LinkedList 有自己處理 clone 方法),讓 clone 後的新物件擁有相同的內容
● 新 LinkedList 加入的成員與舊 LinkedList 相同物件 (HashCode 相同)
// LinkedList.java
private LinkedList<E> superClone() {
try {
return (LinkedList)super.clone();
} catch (CloneNotSupportedException var2) {
throw new InternalError(var2);
}
}
public Object clone() {
LinkedList<E> clone = this.superClone();
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// 在新的 LinkedList 中添加原來物件的內容
for(Node<E> x = this.first; x != null; x = x.next) {
clone.add(x.item);
}
return clone;
}
ArrayList 淺拷貝
● 像是 ArrayList 類就有 Cloneable 界面標示
// ArrayList.java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
... 省略其他方法
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
// Arrays.copyOf 是淺拷貝
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
}
● 使用 Arrays.copyOf 方法,其實內部是使用 Array.newInstance 反射創建對象,再使用 System.arraycopy()
複製物件,但是 仍然為淺拷貝
// Arrays.java
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 將資料複製到 新 Array 中
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
Arrays.copyOf 方法 - 淺拷貝
● 當 Arrays.copyOf 使用在數組對象時,改變數組就會改變原來的對象,代表其實 Arrays.copyOf 只是拷貝了數組的位置 (如果修改物件內容,就會直接改到原來的物件)
import java.util.ArrayList;
import java.util.Arrays;
public class ArrayCopy_Test {
public static void main(String[] args) {
Info[] info = {new Info(9999), new Info(6666)};
Info[] copyInfo = Arrays.copyOf(info, info.length);
System.out.println("Origin ---------------------------");
showInfo(copyInfo);
// 直接修改 copyOf 後的物件
copyInfo[0].mId = 3333;
System.out.println("Clone ---------------------------");
showInfo(copyInfo);
System.out.println("Origin --------------------------- After clone change");
showInfo(info);
}
private static void showInfo(Info...infos) {
for(int i = 0; i < infos.length; i++) {
if(infos[i] == null) {
System.out.println("null");
continue;
}
System.out.println(infos[i].toString());
}
System.out.println();
}
}
class Info {
static class Stamp {
String signature;
}
long mId;
Stamp stamp;
Info(long id) {
mId = id;
}
public Info createStamp(String s) {
stamp = new Stamp();
stamp.signature = s;
return this;
}
@Override
public String toString() {
String base = "id = " + mId;
if(stamp != null) {
base += (", Stamp = " + stamp.signature);
}
return base;
}
}
以下範例,使用
ArrayList#clone
、Arrays.copyOf
操作的結果
● 從上面兩個範例裡面可以看到它改變了兩次,並且都是透過 改變 copy 對象的對象就可以影響到原來的對象,也就證明了 Arrays.copyOf 是複製了對象的地址
--實作結果--
Android Soucre Intent
Intent 傳遞
● Intent 是四大組件傳遞資料的載體 (Activity、Service、Broadcast ...),正是因為有 Intent 才能解開四大組件的偶合 (弱耦合)
private void testIntentClone() {
Intent intent = new Intent();
intent.setData(Uri.parse("https:www.google.com"));
intent.putExtra("GOOGLE_VERSION", 33);
Log.e("TEST123", "\nOriginal: \nData: " + intent.getData() +
"\nGOOGLE_VERSION: " + intent.getIntExtra("GOOGLE_VERSION", -1));
Intent cloneOne = new Intent(intent);
Log.e("TEST123", "\n Clone: \nData: " + cloneOne.getData() +
"\nGOOGLE_VERSION: " + intent.getIntExtra("GOOGLE_VERSION", -1));
}
從結果來看 2 個 Intent 相同
● Intent 類的 clone 方法會發現 它並沒有呼叫 super.clone
方法,因為開發者判斷使用 new 成本較低,從這裡可以看出 對象拷貝的方案是依照建構成本來考量
// Intent.java
@Override
public Object clone() {
// 使用建構函數複製
return new Intent(this);
}
/**
* Copy constructor.
*/
public Intent(Intent o) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
}
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式 Creation Patterns
● 創建模式 PK
● 創建模式 - Creation Patterns
:
創建模式用於「物件的創建」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
● Singleton 單例模式 | 解說實現 | Android Framework Context Service
● Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer
● Factory 工廠方法模式 | 解說實現 | Java 集合設計
● Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗
● Clone 原型模式 | 解說實現 | Android Framework Intent
行為模式 Behavioral Patterns
● 行為模式 PK
● 行為模式 - Behavioral Patterns
:
行為模式關注物件之間的「通信」和「職責分配」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
● Stragety 策略模式 | 解說實現 | Android Framework 動畫
● Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService
● Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞
● Specification 規格模式 | 解說實現 | Query 語句實做
● Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計
● Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存
● Visitor 設計模式 | 實現與解說 | 物件導向設計
● Template 設計模式 | 實現與解說 | 物件導向設計
● Mediator 模式設計 | 實現與解說 | 物件導向設計
● Composite 組合模式 | 實現與解說 | 物件導向設計
● Observer 觀察者模式 | JDK Observer | Android Framework Listview
結構模式 Structural Patterns
● 結構模式 PK
● 結構模式 - Structural Patterns
:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
● Decorate 裝飾模式 | 解說實現 | 物件導向設計
● Iterator 迭代設計 | 解說實現 | 物件導向設計