02 原型模式
Java中怎麼拷貝一個對象呢?
可以通過調用這個對象類型的構造器構造一個新對象,然後將要拷貝對象的屬性設置到新對象裏面。
1.Java中也有另一種不通過構造器來拷貝對象的方式,這種方式稱爲克隆。
2.Java提供了java.lang.Cloneable和java.lang.Object中的clone()方法來支持克隆。
使用條件
是用於創建重複的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
這種模式是實現了一個原型接口,該接口用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如,一個對象需要在一個高代價的數據庫操作之後被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫調用。
寫法
如果希望一個類擁有克隆的行爲,要做一下幾步:
1.該類實現java.lang.Cloneable接口。
2.覆蓋Object的clone方法,將訪問修飾符改爲public,修改返回類型。
3.clone方法內部調用父類的clone方法。
/*
* 1.實現cloneable接口
* */
class Graphy implements Cloneable{
Person writer;
/*2.重寫clone方法,並注意將訪問修飾符改爲public,修改返回類型。*/
@Override
public Graphy clone() throws CloneNotSupportedException {
/*3. clone方法內部調用父類的clone方法。父類執行對對象的字段的克隆操作,強轉修改 返回類型*/
return (Graphy)super.clone();
}
public Person getWriter() {
return writer;
}
public void setWriter(Person writer) {
this.writer = writer;
}
@Override
public String toString() {
return "graphy{" +
"writer=" + writer +
'}';
}
}
# 爲啥這麼寫
## 原理剖析
由於這段源碼很簡單而且註釋寫得太好了,先直接粘貼出來,免得寫得不確切,影響讀者閱讀。
```java
package java.lang;
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
一個類實現Cloneable接口表示可以有對象的克隆的行爲的合法性,如果不實現會拋錯CloneNotSupportedException
但這個接口中卻沒有提供clone方法(Cloneable接口中沒有任何方法,看起來更像是一個標記接口。 )
按照註解提示 我們通常的做法是重寫Object的clone方法並把其改爲public方法。
那麼我們來看看Object的clone方法的源碼和註釋吧
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
創建並返回這個類的克隆,準確的描述是這個類的實例對象的克隆。
流程是 如果這個類的對象已經存在,直接複製一份這個對象,裏面的屬性原封不動的拷貝過來,這涉及到了地址的變化。
如果一個對象沒有實現Cloneable接口,調用它的clone方法會拋出CloneNotSupportedException異常。但數組例外,可以認爲數組實現了Cloneable,所以可以直接調用其clone方法獲取克隆對象。
深克隆與淺克隆(深拷貝與淺拷貝的區別)
按照Object克隆方法描述中的規範,克隆得到對象應該獨立於被克隆對象,從根本上說,它們的內存地址(不嚴格的說)應該不同。但是如果有這種情況,一個可克隆類中包含其他的引用類型屬性,那麼克隆後會是什麼情況呢?舉個例子說明一下:
package xzc._04prototype;
/*
* 總結:
* 1.一個類實現cloneable接口
* 1.1點接口源碼進去 發現這是一個 標記型號接口 就是 裏面沒有任何需要我們實現的接口
* 1.2這是一個標記型接口 用於聲明合法性 不寫拋錯
*
* 2.重寫clong方法
* 2.1 點開源碼會看到 這是一個 protected native 修飾的方法(意味着更底層是c++寫的方法)
* 2.2 提示我們使用clong方法 必須寫實現Cloneable接口 可以看到所有數組默認已經實現了Cloneable接口可以不用我們去寫
* 2.3 重寫Clone方法並在裏面用 super.clone()實現,將訪問修飾符改成public (非必須)修改返回類型爲該類的類型。
*
* 3.深拷貝於淺拷貝
*
* */
/*
* 1.實現cloneable接口
* */
class Graphy implements Cloneable {
Person writer;
/*2.重寫clone方法,並注意將訪問修飾符改爲public,修改返回類型。*/
@Override
public Graphy clone() throws CloneNotSupportedException {
/*3. clone方法內部調用父類的clone方法。父類執行對對象的字段的克隆操作,強轉修改返回類型*/
return (Graphy) super.clone();
}
public Person getWriter() {
return writer;
}
public void setWriter(Person writer) {
this.writer = writer;
}
@Override
public String toString() {
return "graphy{" +
"writer=" + writer +
'}';
}
}
public class prototypeMode {
public static void main(String[] args) throws CloneNotSupportedException {
Graphy graphy = new Graphy();
Person person = new Person();
Graphy graphy_clone = graphy.clone();
//graphy和graphy_clone是否指向相同的內存地址
System.out.println("graphy_clone == graphy");
System.out.println(graphy_clone == graphy);
//graphy的writer和graphy_clone的writer是否指向相同的內存地址
System.out.println("graphy_clone.getWriter() == graphy.getWriter()");
System.out.println(graphy_clone.getWriter() == graphy.getWriter());
}
}
class Person {
String name = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
'}';
}
}
執行結果
可見clone方法默認的行爲就是淺克隆。那麼也很容易想到,如果克隆一個對象,並遞歸的克隆其所有的引用類型屬性,這樣的方式就是深克隆了。
理解了原理我們做如下修改
@Override
public Graphy clone() throws CloneNotSupportedException {
/*3. clone方法內部調用父類的clone方法。父類執行對對象的字段的克隆操作,強轉修改返回類型*/
Graphy graphy= (Graphy) super.clone();
Person person= graphy.getWriter().clone();
graphy.setWriter(person);
return graphy;
}
設置了下一層的遞歸克隆
對其屬性的類實現克隆接口
class Person implements Cloneable{
String name = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Person clone() throws CloneNotSupportedException {
/*3. clone方法內部調用父類的clone方法。父類執行對對象的字段的克隆操作,強轉修改返回類型*/
return (Person)super.clone();
}
@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
'}';
}
}