That's It!

請按左下角 Older Post 看較舊文章,搜尋出來的也可!

Java — clone method 和 copy constructor

事緣 Java 的 clone method (用作複製物件) 一直引起不少熱烈的討論,例如 Effective Java 中的 Item 11 (Override clone judiciously) 中便提到 clone method 的大量缺點,加上還有其他不同的方法來複製物件 (例如: copy constructor、serialization 等),故此很多 Java 程式設計師均不喜愛使用 clone method。

不過,站長則認為 clone method 其實沒有大家想像得那麼差勁。雖然,clone method 亦有不少缺點,但其實這不必令大家避開不用 clone method 而改用 copy constructor。

 

在此先介紹以 clone method 和 copy constructor 複製物件的基本資料。

(1) clone method

clone method 是一個定義在 Object (即所有 reference type 的 superclass) 的 protected native method,用作複製物件之用。假若要實作及使用 clone method,類別必須 implements Cloneable。

且看 documentation:


protected Object clone()
throws CloneNotSupportedException

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 x, the expression:

x.clone() != x

will be true, and that the expression:

x.clone().getClass() == x.getClass()

will be true, but these are not absolute requirements. While it is typically the case that:

x.clone().equals(x)

will be true, this is not an absolute requirement.

也就是,contract 中定義 clone() 將傳回一個物件的複製本。

 

(2) copy constructor

Copy constructor 是指一個以自己類別作為參數的建構子。

java.awt.Dimension 類別為例。Dimension 中包含以下建構子:


public Dimension(Dimension d)

Creates an instance of Dimension whose width and height are the same as for the specified dimension.

Parameters:
d - the specified dimension for the width and height values

也就是使用建構子,用作複製物件的方法。

在 Java API 中,其實有不少的類別均同時有 clone method 和 copy constructor,例如以上提及的 Dimension,以及 Point、ArrayList、TreeSet 等。

 

那問題的重點就出現了。既然有兩種如此不同的複製物件的方法,那一種較好? 以下列出站長個人的看法。當然每一點其實也很有爭議,不過也可先看完,最後在下方留言討論。

clone method 的優點:

(1) 對於一些很簡單的類別,clone method 實作較為簡單。

(2) 能複製陣列 (array)。

(3) (個人認為) 對使用 singleton design pattern 的類別的處理較為合理。

 

clone method 的缺點:

(1) 所有 superclass 均須 implements Cloneable 並實作 clone method,否則很難 (甚至不能) 寫出 clone method (相反 copy constructor 是較易的)。

(2) 要處理額外的 cast 及例外 (exception)。

(3) 無法複製 reference type 的 final field。

(4) 若只有一個 Cloneable 的實例 (instance),無法直接使用 clone method (除非嘗試使用 reflection)。

(5) copy constructor 可使用 this() 來呼叫其他建構子,藉以簡化程式碼 (小優點),但 clone method 不能。

 

以一個簡單的類別 Person 為例,這裡同時使用了 clone method 及 copy constructor 複製物件。

Person (未加入複製的程式碼):

public class Person
{
	private int age;
	public Person(int age)
	{
		super();
		this.age = age;
	}

	public int getAge()
	{
		return this.age;
	}

	public void setAge(int age)
	{
		this.age = age;
	}
}

加入後:

public class Person implements Cloneable
{
	private int age;
	public Person(int age)
	{
		super();
		this.age = age;
	}

	public Person(Person p)
	{
		super();
		this.age = p.age;
	}

	@Override
	public Object clone() throws CloneNotSupportedException
	{
		return super.clone();
	}

	public int getAge()
	{
		return this.age;
	}

	public void setAge(int age)
	{
		this.age = age;
	}
}

沒有複製物件的版本:

public static void main(String[] args)
{
	Person p1 = new Person(30);
	Person p2 = p1;
	p1.setAge(10);
	System.out.println(p2.getAge());
}

顯示 10。這是因為 p1 和 p2 是指向同一物件之故。

若把 main method 改成

public static void main(String[] args)
{
	Person p1 = new Person(30);
	Person p2 = new Person(p1);
	p1.setAge(10);
	System.out.println(p2.getAge());
}

public static void main(String[] args) throws CloneNotSupportedException
{
	Person p1 = new Person(30);
	Person p2 = (Person)p1.clone();
	p1.setAge(10);
	System.out.println(p2.getAge());
}

則會傳回 30。這是因為 p1 和 p2 是兩個不同的 instance,它們的 age 也是不同的。

這裡同時展示了如何使用 clone 和 copy constructor。就 clone 而言,super.clone() 將呼叫 Object 中的 clone method。首先由於 Object 的 clone method 是 protected,故身為 subclass 的 Person 可以存取。Object 中的 clone 是一個特別的 native method,會存回一個與呼叫者同一類別的 instance (即 Person),當中:

(1) 所有 primitive type 的 field 將自動複製。

(2) 所有 reference type 的 field 只會複製 reference。

這也是我們常說,clone method 只會製出淺層複製 (shallow copy) 的意思。

由於 Person implements Cloneable,所以不會出現 CloneNotSupportedException,可以忽略。

以向下轉型 (cast) 便可獲得一個 Person 的 instance (即 p2)。所以即使在 p1 修改 age,也不會影響 p2。

而 copy constructor 則是建構出一個新的 Person,然後把 age 修改成參數的 age。所以 p2 是一個新的 instance,而且所有成員 (field,在這裡只有 age 1個) 均要手動複製。

 

如果以上面這個例子來看,clone method 好像有少許複雜。不過,clone method 其實可以從以下兩方面簡化:

(1) 自從 1.5 版本,Java 推出了 covariant return type,所以 clone 可以傳回較 narrow 的 subclass,也能成功覆載 (override) clone method。

(2) 其實 CloneNotSupportedException 並不會無故出現,所以可以直接在 clone 內 catch 掉。

@Override
public Person clone()
{
	try
	{
		return (Person)super.clone();
	}
	catch (CloneNotSupportedException ex)
	{
		//won't happen
		throw new InternalError();
	}
}

這樣寫是沒有問題的。首先 super.clone() 一定傳回一個 Person 的實例,所以不用每次均向下轉型。另外因為 CloneNotSupportedException 只會在類別未 implements Cloneable 時才會出現,故除非程式出錯,否則程式不會執行到 throw new InternalError() 的部分。

這樣寫後,main method 便可以改成:

public static void main(String[] args)
{
	Person p1 = new Person(30);
	Person p2 = p1.clone();
	p1.setAge(10);
	System.out.println(p2.getAge());
}

也不用特意在 main 中加入 throws CloneNotSupportedException。

因此,關於 additional cast 的缺點,其實不會出現 (因為在 clone method 中 cast 一次便夠了)。當然,在某些較舊的類別,例如在 1.2 中引入的 Collection framework 中的 ArrayList、HashSet 等,傳回的依然是 Object。相反,一些較新的類別,如 java.util.concurrent.ConcurrentSkipListMap (SortedMap 的 concurrent 版本,內部以 skip list 運作) 中的 clone method 則傳回一個自己類別的實例,不用再 cast。

 

clone method 在較簡單的類別的優點:

其實對於一些較簡單的類別,使用 clone method 來處理是較為精簡的,不過卻有其他問題。考慮以下 Book 類別 (只是亂作,為了簡化的關係不加入 getter 和 setter method):

public class Book
{
	private String name, authorName;
	private String[] titles;
	private double price;
	private int pageNo;
	public Book(String name, String authorName, String[] titles, double price, int pageNo)
	{
		super();
		this.name = name;
		this.authorName = authorName;
		this.titles = titles;
		this.price = price;
		this.pageNo = pageNo;
	}
}

假若以 clone method 來複製 Book 的 instance 的話,由於 String 是不可變 (immutable) 的類別,而 int 和 double 均是 primitive type,

public class Book implements Cloneable
{
	...
	@Override
	public Book clone()
	{
		try
		{
			Book book = (Book)super.clone();
			book.titles = titles.clone();
			return book;
		}
		catch (CloneNotSupportedException ex)
		{
			//must succeed
			throw new InternalError();
		}
	}
}

根據 Object 內 clone method 的說明,所有陣列 (array) 均自動 implements Cloneable,所以 String[] 可直接複製 (注意: 這裏不用向下轉型)。另外由於其他的 fields 均是 immutable 的,故不用複製也可以。

但假若以 copy constructor 來複製 Book 的話,程式便可能較複雜了,例如:

public Book(Book book)
{
	this(book.name, book.authorName, book.titles.clone(), book.price, book.pageNo);
}

看下去似乎簡單,不過假若 immutable fields 的數量不斷增加的話,clone method 不用特意處理 immutable fields 的優點便能突顯出來。

另外其實上面的方法有兩個問題:

(1) 其實也使用了 clone method 於陣列上。假若大家十分抗拒的話,則要用下面方法。

(2) 假若 Book 中的 fields 是使用 setter method (例如:

public void setPageNumber(int pageNo)
{
	this.pageNo = pageNo;
}

的話),便不能直接以 this 呼叫類別中的其他建構子。

所以,最壞的情況是

public Book(Book book)
{
	super();
	this.name = book.name;
	this.authorName = book.authorName;
	int length = book.titles.length;
	this.titles = new String[length];
	System.arraycopy(book.titles,0,this.titles,0,length);
	this.price = book.price;
	this.pageNo = book.pageNo;
}

另一個方法是用 java.util.Arrays 中的 copyOf method 來複製陣列,不過和 clone method 沒有太大分別。

 

看似是 clone method 較好,不過 clone method 有一個較大的缺點,就是無法複製 final fields。以下說明一個 clone method 無法成功的例子。

public class Container
{
	private final double[] numbers;
	public Container(double[] numbers)
	{
		super();
		this.numbers = numbers;
	}
}

由於 final fields 在建構子中是可以 assign 一次的,所以在建構子中 assign 是沒有問題的。

假若使用 copy constructor 來複製 Container 的實例的話,

public Container(Container c)
{
	this(c.numbers);
}

public Container(Container c)
{
	super();
	int length = c.numbers.length;
	double[] d = new double[length];
	System.arraycopy(c.numbers,0,d,0,length);
	this.numbers = d;
}

便可。不過如果使用 clone method 的話,

@Override
public Container clone()
{
	try
	{
		Container c = (Container)super.clone();
		c.numbers = this.numbers.clone();
		return c;
	}
	catch (CloneNotSupportedException ex)
	{
		//must succeed
		throw new InternalError();
	}
}

是無法成功編譯 (compile) 的。因為 numbers 被宣告 (declare) 為 final,所以無法修改。假若真的要成功編寫出 clone method 的話,唯有放棄不使用 final。

不過也有三點要注意的:

(1) 假若 Container 被設計成不可變 (immutable) 的類別的話,其實複製一個 Container 的實例的機會可算是十分小,所以 final 的問題只會在一些較大型,同時包含 final 及 non-final 的 mutable 類別中出現。

(2) 若 Container 真的要 immutable,在建構子中,numbers 參數應先被複製,否則 numbers 依然可能在往後被修改。所以,若要複製 numbers,其實也要使用 clone method。

(3) 由於複製物件時不用額外複製 immutable 的 final fields,所以假若類別中的 final fields 只包含 immutable 的,則也可使用 clone method,例如:

public class Container implements Cloneable
{
	private final int containerCode;
	private double[] numbers;
	public Container(int containerCode, double[] numbers)
	{
		super();
		this.containerCode = containerCode;
		this.numbers = numbers;
	}

	@Override
	public Container clone()
	{
		try
		{
			Container c = (Container)super.clone();
			c.numbers = this.numbers.clone();
			return c;
		}
		catch (CloneNotSupportedException ex)
		{
			//must succeed
			throw new InternalError();
		}
	}
}

是沒有問題的。

所以,最後 clone method 的限制其實不算很大,而 copy constructor 和 clone method 也可以混合使用。

 

有關繼承的問題

在繼承方面,clone method 亦有不少可討論的重點。

(1) 關於 singleton pattern

其實 singleton pattern 是非常常用的程式設計模式 (design pattern)。假若採用 singleton pattern 的類別沒有父類別 (superclass),那當然可以使用 enum type 來簡單地實現。不過,有時採用 singleton pattern 的類別也有可能繼承自其他類別。

由於採用 singleton pattern 的類別必須只有一個實例,故不能允許使用者複製實例。且看以下例子:

import java.util.*;

public final class SingletonSet extends HashSet
{
	private static final SingletonSet INSTANCE = new SingletonSet();
	private SingletonSet()
	{
		super();
		//some operation
	}

	public static SingletonSet getInstance()
	{
		return INSTANCE;
	}
}

程式編寫員必須禁止使用者以 clone method 複製實例。由於 HashSet implements Cloneable,所以便要覆載 clone method,並 throws CloneNotSupportedException。不過這卻有個麻煩的地方,就是由於 HashSet 中的 clone method 已在內部處理 CloneNotSupportedException,所以無法直接在 SingletonSet 中 throws 出。

所以,如果這樣寫的話,是不能編譯的。

@Override
public Object clone() throws CloneNotSupportedException
{
	throw new CloneNotSupportedException();
}

若要一個較為折衷的方法,可以

@Override
public Object clone()
{
	throw new RuntimeException(new CloneNotSupportedException());
}

出現的結果:

當然 copy constructor 就沒有這個問題,因為子類別是不會自動繼承建構子的。不過,SingletonSet 卻可以成為 HashSet 中 copy constructor 的參數,當然傳回的是 HashSet 而不是 SingletonSet,所以嚴格來說並沒有違 singleton pattern 的原則,這就看大家的喜好了。還有,另一個可用的 exception 為 UnsupportedOperationException,特別適用於 Java Collection Framework 內的類別。

(2) 關於父類別的問題

假若以 clone method 來複製物件,clone method 內必須使用 super.clone() 呼叫父類別的 clone method,否則傳回物件的類別可能和原先的不同 (當然,根據 API 定義, x.clone().getClass() == x.getClass() 並非絕對要求,但卻是用家預期之內的)。

例子:

class Product implements Cloneable
{
	double price;
	Product() //default constructor
	{
		super();
	}

	/*
	 * some methods
	 */

	@Override
	public Product clone()
	{
		try
		{
			return (Product)super.clone();
		}
		catch (CloneNotSupportedException ex)
		{
			throw new InternalError();
		}
	}
}

class Phone extends Product
{
	double weight;
	Phone()
	{
		super();
	}

	/*
	 * some methods
	 */

	@Override
	public Phone clone()
	{
		return (Phone)super.clone();
	}
}

在 Phone 中的 clone method 能直接呼叫 super.clone() 然後向下轉型 (cast) 並直接傳回實例的原因,是因為 Product 中的 clone method 也是呼叫父類別 (Object) 的 clone method,而 Object 中的 clone method 是特別的 native method,能傳回呼叫者的真實類別,所以我們可以肯定,Phone 中即使呼叫 super.clone(),只要像 API 中提及的慣例:

By convention, the returned object should be obtained by calling super.clone. If a class and all of its superclasses (except Object) obey this convention, it will be the case that x.clone().getClass() == x.getClass().

,直接向下轉型是沒有問題的。

不過假若 Product 中的 clone method 並非以呼叫 super.clone() 的方式實現的話,則有問題了。

以下例子只改了 Product 中的 clone method,不過複製 Phone 實例時,卻出現 ClassCastException。把 clone method 改為

@Override
public Product clone()
{
	Product p = new Product();
	p.price = this.price;
	return p;
}

Product 的實例依然可以正常複製,不過 Phone 的則不能。加入以下 main method,執行時出現 ClassCastException。

public static void main(String[] args)
{
	Phone p = new Phone();
	p.price = 5;
	Phone p2 = p.clone();
	p.price = 10;
	System.out.println(p2.price);
}

原因很明顯: 因為 Product 中的 clone method 所傳回的實例並非 Phone,所以無法向下轉型。

所以,使用 clone method 的限制便出現了: 除非類別本身為 final (即不會再有子類別),只要父類別並沒有實作 clone method,類別本身是無法寫出正常的 clone method 的 (因為無法呼叫 super.clone(),將會令子類別不能使用 super.clone() 了)。而除非我們特意使用 reflection,否則無法直接呼叫 Object 中的 clone method。

當然,使用 copy constructor 則沒有這個問題,因為所有建構子均需呼叫父類別的建構子,而且傳回的必定是同一類別。

(2)(續) 關於父類別的問題

其實問題還有 private fields。

因為上述的問題,還有一個解決方案,就是把 Phone 中的 clone method 宣告成 final,然後以 copy constructor 的形式傳回 Phone。假若覆載 (override) Phone 中的 clone method 是不允許的,Phone 的子類別的 clone method 將只能傳回 Phone,雖然違反了慣例,但亦是 contract 容許的。

不過,對於複製 private fields,其困難則進一步增加。且看以下修改了少許的例子。

import java.awt.*;

class Product
{
	private Dimension size;
	Product(Dimension size)
	{
		super();
		this.size = size;
	}
}

class Phone extends Product implements Cloneable
{
	double weight;
	Phone(Dimension size)
	{
		super(size);
	}

	@Override
	public Phone clone()
	{
		try
		{
			return (Phone)super.clone();
		}
		catch (CloneNotSupportedException ex)
		{
			throw new InternalError();
		}
	}
}

修改的地方有數處。首先,Product 並沒有 implements Cloneable。第二,我們以 super.clone() 而不是 copy constructor 來複製 Phone 的實例。

以上的例子是能夠成功編譯的,不過 Phone 中的 clone method 則無法製出深層的複製實例。這是因為當實例在 Phone 中呼叫 super.clone() 時,將呼叫 Product 中的 clone method,但因 Product 類別沒有覆載 clone method,Object 中的 clone method 便自動地呼叫。

但如果這樣寫 clone method,Product 中的 size 便不能複製了。由於 size 是 Dimension 的實例 (是 mutable type),我們必須複製。而 Phone 中的 clone method 是未處理 size 的。在 Product 中加入以下 method 然後執行程式:

public static void main(String[] args)
{
	Phone p = new Phone(new Dimension(6,7));
	Phone p2 = p.clone();
	((Product)p).size.width = 3;
	((Product)p).size.height = 5;
	System.out.println(((Product)p2).size);
}

得出的 size 還是 (3,5),可見 size 還未複製。

解決方法是,把所有父類別中的 private mutable fields 改為 protected,這樣子類別便可以存取。以上的例子中,要把 size 由 private 改為 protected。接著,把 clone method 改成

@Override
public Phone clone()
{
	try
	{
		Phone p = (Phone)super.clone();
		p.size = (Dimension)this.size.clone();
		return p;
	}
	catch (CloneNotSupportedException ex)
	{
		throw new InternalError();
	}
}

以複製 size。

當然這樣的做法還有三個問題。其一,假若我們不能修改父類別 (例如: Java API 中沒有 implements Cloneable 的類別),便不能使用這個方法。另外,有時我們未必知道所有父類別的 private fields,所以寫出 clone method 亦較困難。還有,把 private fields 改為 protected,父類別的內部結構可能會被不必要地修改,破壞了封裝 (encapsulation) 的原意。

不過,使用 copy constructor,由於我們始終也要存取 private fields,除非父類別中有 copy constructor,否則依然不能複製。

故此,較好的寫法有兩種: 一種是父類別和子類別均提供 clone method,另一種是兩者均提供 copy constructor。

(1) clone method 的解決方案:

import java.awt.*;

class Product implements Cloneable
{
	private Dimension size;
	Product(Dimension size)
	{
		super();
		this.size = size;
	}

	@Override
	public Product clone()
	{
		try
		{
			Product p = (Product)(super.clone());
			p.size = (Dimension)this.size.clone();
			return p;
		}
		catch (CloneNotSupportedException ex)
		{
			throw new InternalError();
		}
	}
}

class Phone extends Product
{
	double weight;
	Phone(Dimension size)
	{
		super(size);
	}

	@Override
	public Phone clone()
	{
		return (Phone)super.clone();
	}
}

注意: Phone 中的 weight 由於是 primitive data type,所以會自動複製,不用額外處理。

(2) copy constructor 的解決方案:

import java.awt.*;

class Product implements Cloneable
{
	private Dimension size;
	Product(Dimension size)
	{
		super();
		this.size = size;
	}

	Product(Product p)
	{
		this(new Dimension(p.size));
	}
}

class Phone extends Product
{
	double weight;
	Phone(Dimension size)
	{
		super(size);
	}

	Phone(Phone p)
	{
		super(p);
		this.weight = p.weight;
	}
}

有關 Cloneable 的先天缺陷

Cloneable 作為一個 marker interface (沒有定義 method 的 interface),亦是個先天缺陷。

在 Java 中,出現 interface 這種特別的類別,背後有一個很大的作用,就是我們可以以 interface 當為普通的類別 (class) 然後執行某個特定的 method,不用特別介意所採用的 implementation,以及實際物件的類別是什麼。以 Java Collections Framework 為例,我們獲取了一個 List 的實例,這個實例可以是 ArrayList、LinkedList、Vector、CopyOnWriteArrayList 等,但我們依然可以使用 contains() method 來測試 List 實例中有沒有包含某物件。

但對於 marker interface 而言,我們不能這樣做。其實最初 Java 把 Cloneable 設計為 marker interface 而把 clone method 定義在 Object 中,很可能是因為 Java 的 interface 內無法定義實體 (concrete) (non-abstract) 的 method,但 clone method 則是個 native method,亦算為實體的。例如:

public interface Test
{
	native void foo();
}

是不能成功編譯的。Java 的 interface 內的 method 只能為 abstract (直到 1.8)。

本來 clone method 定義在 Object 內亦沒有大問題,不過由於不是所有類別均會實作 clone method (因為某些類別的物件不能被複製),像 Thread 等,類別亦可能需要額外在 clone method 傳回的實例中再作修改 (像上文所述),故 clone method 不能被定義為 public method,較合理的是宣告為 protected,只有子類別可以存取 (或覆載為 public)。

但問題來了。這樣的設計令我們不能單靠得知一個實例是 Cloneable 便呼叫 clone method。且看例子:

import java.awt.*;
import java.util.*;

public class Test
{
	public static void main(String[] args)
	{
		ArrayList<Cloneable> list = new ArrayList<>();
		list.add(new Vector<String>());
		list.add(new Point(3,5));
		list.add(new Dimension(1024,768));
		for (Cloneable c: list)
		{
			System.out.println(c.clone());
		}
	}
}

以上例子是不能成功編譯的。出現

Test.java:14: error: cannot find symbol

的錯誤,是因為單單得知 c 是 Cloneable 的實例,並不代表 clone method 能被呼叫。

假若我們把 for 中的語句改為

Object o = c;
System.out.println(o.clone());

則會出現

Test.java:15: error: clone() has protected access in Object

的錯誤。可見最終我們無法容易地複製 Cloneable 的實例,除非使用 reflection。不過 Cloneable 說明中亦有提及:

Note that this interface does not contain the clone 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.

故此,即使利用 reflection,也未必成功。

import java.awt.*;
import java.util.*;
import java.lang.reflect.*;

public class Test
{
	public static void main(String[] args) throws Exception
	{
		ArrayList<Cloneable> list = new ArrayList<>();
		list.add(new Vector<String>());
		list.add(new Point(3,5));
		list.add(new Dimension(1024,768));
		for (Cloneable c: list)
		{
			Method method = c.getClass().getMethod("clone");
			System.out.println(method.invoke(c,new Object[0]));
		}
	}
}

看似是 clone method 的缺點,不過不要忘記,假若我們獲得一個「有 copy constructor 類別」的 List,除非使用 reflection,否則依然也無法複製物件。所以,其實這也不算什麼問題,因為好像再沒有什麼解決方法了。

以上的問題還會嚴重下去。由於先天設計問題,在很多抽象類別中均無法使用 clone method。

由於 Cloneable 內沒有定義 clone method,所以單靠某 interface extends Cloneable 來令實體類別實作 clone method 是不可能的。如果大家習慣使用 interface 而不用實際的類別來編寫程式 (亦是符合一般的 abstraction principle),那就會有問題了。

例如以下的語句均能成功編譯:

ArrayList<String> list = new ArrayList<>();
@SuppressWarnings("unchecked")
ArrayList<String> cloned = (ArrayList<String>)(list.clone());

TreeSet<Integer> set = new TreeSet<>();
@SuppressWarnings("unchecked")
TreeSet<Integer> cloned = (TreeSet<Integer>)(set.clone());

ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>();
ConcurrentSkipListMap<String, String> cloned = map.clone();

但假若我們使用 interface List、Set 等,便會出現問題了:

List<String> list = new LinkedList<>();
Object cloned = list.clone();

雖然 LinkedList implements Cloneable,但 List 作為 interface 沒有 extends Cloneable,所以即使幾乎所有 List 的 implementation 均有實作 clone method,以上的寫法是不能成功編譯。

這個情況在 copy constructor 較易處理。以 ArrayList 為例,其建構子能接受 Collection 作為參數,包括所有 List 和 Set。

 

此外,關於 Cloneable 還有一個小問題,就是有些人 (例如這裡這裡) 會認為 “Cloneable" 這個字是串錯了的 (較好的串法應該是 Clonable,少了一個 e)。不過,既然 “Cloneable" 是人工創造的字,有否串錯問題也不大,只要能用便可。另外同一個情況在其他 Java 的 interface 並沒有一致的標準,例如: AutoCloseable (有 e)、Comparable (沒有 e)、Serializable 等 (沒有 e)。

 

所以在此總結一下:

(1) 為了方便起見,若大家打算編寫程式庫 (library) 的話,最好的方法,是在類別中同時提供 clone method 和 copy constructor,並且跟從兩者慣例。其實這做法是非常常見,如文章最初所言的 Collection framework 內的類別、Dimension、Point 等。

(2) 對使用者而言,父類別有 copy constructor 則用 copy constructor,有 clone method 則用 clone method,但不須特意使用/不使用哪一個。

(3) 站長個人認為,對簡單的類別,clone method 還是較為優雅。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

Information

This entry was posted on 2015/06/01 by in Java 教學tony200910041.

分類

Night

六月 2015
« 五月   七月 »
 123456
78910111213
14151617181920
21222324252627
282930  
Dropbox 載點暫時掛了,請嘗試使用其他載點!

%d 位部落客按了讚: