2011年3月21日 星期一

[ Java 文章收集 ] Java沒有指標? - 淺談參考(Reference)與物件的複製(Object copy)


轉載自 這裡
前言 :
來看一段簡單的範例程式 :
- C++ 代碼 :
  1. /* main.cpp */  
  2. #include "Point.h"  
  3.   
  4. int main() {  
  5.   Point p1(55);  
  6.   Point p2 = p1;  
  7.   cout << "p1 is : " << p1 << endl;  
  8.   cout << "p2 is : " << p2 << endl;   
  9.     
  10.   p1.setX(500);  
  11.   p1.setY(500);  
  12.   cout << "p1 is : " << p1 << endl;  
  13.   cout << "p2 is : " << p2 << endl;  
  14.   return 0;  
  15. }  
- Java 代碼
  1. /* main.java */  
  2.   
  3. public class Main {   
  4.   public static void main(String args[]) {  
  5.     Point p1 = new Point(5,5);  
  6.     Point p2 = p1;  
  7.     System.out.println("p1 is : " + p1);  
  8.     System.out.println("p2 is : " + p2);  
  9.       
  10.     p1.setX(500);  
  11.     p1.setY(500);  
  12.     System.out.println("p1 is : " + p1);  
  13.     System.out.println("p2 is : " + p2);  
  14.   }  
  15. }  

猜猜它們的結果會不會一樣!!! 如果你不熟析Java,那麼它的結果可能會讓你有點吃驚 :

在Java中,p2的內容也一併被修改了! 這是因為在Java中,所有的物件,即所有非原生資料型態(primitive data type)的變數,都是參考!

參考是什麼 :
何謂參考(reference)?wiki中對參考的解釋,我覺得寫的不錯:「a reference is an object containing information about how to locate and access the particular data item, as opposed to containing the data itself.」
參考的概念很像指標,它們同樣只保存關於「如何存取某筆資料」的資訊(像是指標中所指向的記憶體位址);但並不保存「該筆資料」本身。
因此,上述的範例中,兩種語言在寫法上雖然非常相似,但是所描述的行為卻是不同的。
我下方的圖簡單說明 :


在C++中,「p2 = p1」的意義是將p1物件的內容複製一份給p2;
然而在Java中,p2與p1都是參考,因此「p2 = p1」的意義是將p1所指向的物件的參考複製一份給p2
這樣的作法有點類似C/C++中的指標. 若將上述的C++範例改寫為使用指標的話 :
  1. Point* p1 = new Point(5,5);  
  2. Point* p2 = p1;  
  3. ...  
  4. p1->setX(500);  
  5. ...  
那其行為與結果就會與上述的Java範例中類似了.

C++與Java中的參考 :
每種程式語言對於參考的支援也都不盡相同。
在Java中所有的物件都是一個參考,這是Java的特性之一。Java的參考不支援算術運算,也不能對其取址,不過可以改變該參考所指之處. 像上述的Java範例中,你可以將p2改為指向p3所指向的物件實體 :


如此一來,p2與p3所指向的就是同一個物件實體了. C++中也支援參考,寫法如下 :


事實上,C++的參考,比較常用的場合是在呼叫函式時,傳遞參數給函式的時候,稱為call by reference。在C++的標準函式庫中,很常見到這樣的作法,如srting類別的compare函式 :
int string::compare( const string& str ) const;

這樣做的好處,與C的傳址呼叫(call by address)是一樣的,就是避免傳遞大型資料結構時,複製資料所需的時間. 而需要注意的事也一樣,就是在函式中若修改了該參數的內容,則原本作為參數的變數也會一併被修改。這也是為什麼compare的str參數前面還加上了const.

Java 物件的複製 :
那麼,在Java中,要如何做才能真正複製一個物件呢?答案是使用Object類別的clone方法。
首先你必須先實作Cloneable這個介面 :
public class Point implements Cloneable{
...

幸運的是Cloneable本身並沒有定義任何成員需要實作. 但你還需要覆寫(override)Object類別中的public Object clone()這個方法(method) :
  1. public Object clone() throws CloneNotSupportedException {  
  2.   return super.clone();  
  3. }  
接著,只要把p2 = p1改成 :
Point p2 = (Point) p1.clone();

通常就可以了...為什麼說通常呢?因為物件複製(object copy)的問題還不僅如此, 上例中以super呼叫了父類別Object的clone方法來複製物件,但是Object類別的clone方法執行的是shallow copy,也就是說,如果當欲複製之物件的成員中,也有物件的時候,該成員只會將其參考複製而已
(反之,則稱為deep copy。)
如果你希望該成員也要進行複製,那麼你也必須針對該成員所屬的類別作一樣的事 : 實作Cloneable介面、覆寫clone方法!
同樣的問題連C++也會遇到,就是當欲複製之物件的成員中,有指標的時候。
C++中的同類別的物件在複製的時候,是透過copy constructor或copy assignment operator,兩者都是C++的special member functions,由編譯器(compiler)自動實作,而有需求的話programmer可自行override.
而預設上,無論是copy constructor還是copy assignment operator都是執行shallow copy。因此,若你希望執行deep copy,則必須將兩者覆寫.

補充說明 :
[C++ 小學堂] 類別 : 複製構造函數
使用類創建對象時, 構造函數被自動調用來完成對像的初始化, 那麼是否能夠像簡單變量的初始化一樣, 直接用一個對像來初始化另一個對像呢? 複製構造函數就是答案.
This message was edited 2 times. Last update was at 21/03/2011 15:20:31

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...