• 沒有找到結果。

83

在Java中,想要釋放物件實體,只要將 所有參考到該物件實體的物件變數都設 為null或指向其他實體,則Java的回收 機制就會在一段時間後自動回收該物件 實體的記憶體空間。

而其他程式語言(例如C++)則提供了delete與解 構函式,需要使用者手動關心物件釋放的後續動 作。

7.6.3回收物件實體

在呼叫函式時,引數也可以是物件

在前面的範例中,我們曾經示範過傳遞字串物件

。當時我們曾經提及,傳遞字串物件,事實上採 用的是傳「參考值」呼叫

因為物件變數本身是一個參考而非實體。

當我們要傳遞一個自行定義的類別所宣告的物件 變數時,同樣採用的是傳「參考值」呼叫,因此

,呼叫端與被呼叫端可以共用一個物件實體。

在下面這個範例中,我們將傳遞物件,並且將使

用成員函式多載進行不同資料型態的加法。

85

【觀念範例7-7】:使用多 載設計成員函式,並且接 受引數為其他類別的物件

。在本範例中,我們將把 加法函式擴充到二維向量 的加法,範例如右:

範例7-7:ch7_07.java(

隨書光碟

myJava\ch07\ch7_07.java

7.7物件引數的傳參考值呼叫

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

/* 檔名:ch7_07.java 功能:傳遞物件與接收物件 */

package myJava.ch07;

import java.lang.*;

public class ch7_07 //主類別 {

public static void main(String args[]) {

CVector2 i = new CVector2();

i.set(20,40);

CVector2 j = new CVector2();

j.set(15,45);

CVector2 k; //k是CVector2物件變數,在第16行用來接收回傳值 CMyClass X = new CMyClass();

k=X.sum(i,j);

System.out.println("Vector i=(" + i.x + "," + i.y + ")");

System.out.println("Vector j=(" + j.x + "," + j.y + ")");

System.out.println("Vector k=i+j=(" + k.x + "," + k.y + ")");

} }

7.7物件引數的傳參考值呼叫

Vector i=(20,40) Vector j=(15,45) Vector k=i+j=(35,85)

執行結果

87

7.7物件引數的傳參考值呼叫

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

class CMyClass {

public int sum(int a,int b) {

return a+b;

}

public double sum(double a,double b) {

return a+b;

}

public CVector2 sum(CVector2 a,CVector2 b)//二維向量的加法成員函式 {

CVector2 tempVector = new CVector2();

tempVector.x=a.x+b.x;

tempVector.y=a.y+b.y;

return tempVector;

} }

class CVector2 //二維向量類別 {

public int x,y; //二維向量的兩項元素資料

public void set(int m,int n) //用於設定二維向量的兩項元素資料 {

x=m;

y=n;

} }

範例說明:

(1)我們在第43~51行,宣告了另一個類別CVector2,用來代 表二維向量。並提供一個set成員函式在第46~50行。

(2)除了整數與浮點數的加法,在第34~40行,我們又增加了 CMyClass的另一個成員函式sum,它接受的引數是兩個

CVector2類別的物件,並且回傳一個CVector2類別的物件,

它會做二維向量的加法,並回傳二維向量。

(3)第10~11行,宣告一個CVector2類別的物件i並產生實體

,且設定成員變數為(20,40)。

(4)第12~13行,宣告一個CVector2類別的物件j並產生實體

,且設定成員變數為(15,45)。

(5)第14行,宣告一個CVector2類別的物件變數k。

(6)第15行,宣告一個CMyClass類別的物件X並產生實體。

7.7物件引數的傳參考值呼叫

89

(7)第16行,透過X物件的sum成員函式,幫我們做二維 向量的加法,所以k向量=(35,85)。

(8)在第16行的函式呼叫,i物件的參考傳送給對應的a 物件參考,故i與a實際上指向同一個物件實體。j與b亦 同理。

(9)在第14行時,k是一個物件參考,初始值為null,而 第16行時,由於第39行會回傳一個物件實體位址,所以 k儲存該位址後,可以指向物件的實體。

在上一個範例中,雖然i與a指向同一個物件,j與 b指向同一個物件,但依據的是傳「參考值」呼叫

對於Java的傳「參考值」呼叫,每一年都會引起論壇的 討論,爭辯傳參考呼叫與傳參考值呼叫的差別。

7.7物件引數的傳參考值呼叫

傳參考呼叫(Pass by reference)一般而言,指的是程式語言 功能中的傳址呼叫(Call by address)

其特色是被呼叫端的參數會影響呼叫端的引數,如C++與C#都提 供此一功能。

傳值呼叫(Pass by value)則是程式語言功能中的傳值呼叫 (Call by value)

其特色是被呼叫端的參數不會影響呼叫端的引數,大多數的程 式語言都提供了此一功能,如Java。

由於傳址呼叫所能達到的效果,在被呼叫端需要影響呼叫端 兩個以上的變數時非常有用(因為函式回傳值只能傳遞一個 值),因此程式語言必須提供傳址呼叫

若未提供傳址呼叫,則必須提供其他機制來達到同樣的效果 而所謂其他機制,則不可避免地仍需要傳遞一個記憶體位址才 可能達到同樣的效果

例如C語言就是透過傳指標呼叫(Pass by pointer)來達成此一效果。

7.7物件引數的傳參考值呼叫

91

Java的傳「參考值」呼叫則與C語言的傳指標呼叫(Pass by pointer)類似

它雖然也是透過引數傳遞一個位址給被呼叫端的參數

,但即便參數所保存的位址值(參考值)改變了,也 不會影響引數所保存的位址值(參考值)。

換句話說,傳「參考值」呼叫仍屬於傳值呼叫(Call by value)的一種,只不過這個值是一個參考罷了。

所以傳參考呼叫與傳參考值呼叫是不同的兩種機制。

因此,在眾多的Java原文技術文件中,都會提及,Java只支 援傳值呼叫,這是因為傳「參考值」呼叫仍屬於傳值呼叫 (Call by value),而不是傳址呼叫(Call by address)。

以上,在上一章介紹Java傳遞陣列時,我們已經說明過 了,下面這個範例,將會證明Java在傳遞物件或陣列時

,採用的只是傳「參考值」呼叫,而非傳參考呼叫。

7.7物件引數的傳參考值呼叫

假設Java提供了傳參考呼叫(傳址呼叫

),我們可以很容易的製作一個

swap(i,j)函式,用以將i,j互相對調。

以下,我們嘗試製作一個swap函式將兩個引數(物 件參考i,物件參考j)對調看看。

【觀念範例7-8】:嘗試設計一個swap函式互換兩 個物件參考。

範例7-8:ch7_08.java(隨書光碟 myJava\ch07\ch7_08.java)

7.7物件引數的傳參考值呼叫

93 1

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

/* 檔名:ch7_08.java 功能:物件的傳參考值呼叫 */

package myJava.ch07;

import java.lang.*;

public class ch7_08 //主類別 {

public static void main(String args[]) {

CVector2 i = new CVector2();

i.set(20,40);

CVector2 j = new CVector2();

j.set(15,45);

System.out.println("---原始---");

System.out.println("Vector i=(" + i.x + "," + i.y + ")");

System.out.println("Vector j=(" + j.x + "," + j.y + ")");

CMyClass X = new CMyClass();

X.swap(i,j);

System.out.println("---swap後---");

System.out.println("Vector i=(" + i.x + "," + i.y + ")");

System.out.println("Vector j=(" + j.x + "," + j.y + ")");

} }

7.7物件引數的傳參考值呼叫

‐‐‐‐‐原始‐‐‐‐‐‐

Vector i=(20,40) Vector j=(15,45)

‐‐‐‐‐swap後‐‐‐‐‐‐‐

Vector i=(20,40) Vector j=(15,45)

執行結果

範例說明:

(1)從執行結果中,我們發現對調失敗了,為什麼會這樣呢

?我們以記憶體的變化來說明。

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

class CMyClass {

public void swap(CVector2 a,CVector2 b) //互換函式 {

CVector2 temp;

temp=a;

a=b;

b=temp;

} }

class CVector2 //二維向量類別 {

public int x,y; //二維向量的兩項元素資料

public void set(int m,int n) //用於設定二維向量的兩項元素資料 {

x=m;

y=n;

} }

7.7物件引數的傳參考值呼叫

95

(2)當第10~17行執行完畢時,記憶體內容呈現下圖狀況

,i指向3100的物件實體,j指向3600的物件實體。

7.7物件引數的傳參考值呼叫

(3)第18行的X.swap(i,j);會把i與j的參考傳送給a與b

,如下圖。然後開始執行第28~33行的程式。

7.7物件引數的傳參考值呼叫

97

(4)第29行會配置一個可存放參考的記憶體給temp,第 30行則會讓a的參考設定給temp,使得兩者都指向同一 個物件。第30行執行完畢時,結果如下圖:

7.7物件引數的傳參考值呼叫

(5)第31行則會讓b的參考設定給a,使得兩者都指向同 一個物件。第31行執行完畢時,結果如下圖:

7.7物件引數的傳參考值呼叫

99

(6)第32行則會讓temp的參考設定給b,使得兩者都指向 同一個物件。第32行執行完畢時,結果如下圖:(a與b 的參考已經互換完成)

7.7物件引數的傳參考值呼叫

(7)第33行執行完畢時會返回函式呼叫處,此時,由於 參數a,b及函式內的temp都屬於區域變數,故會被釋放

。故a與b的互換根本沒有意義,因為i與j並沒有互換,

記憶體內容和執行函式呼叫前是完全相同的。所以印出 的結果自然是沒有互換成功的結果。

(8)雖然我們互換失敗,但確實在swap中,若透過a或b 或temp修改物件實體的變數內容的話,則呼叫返回後,

i或j的物件實體內容也會被更動。但這並非傳統所謂的 傳參考呼叫(pass by reference),頂多只能稱的上是 傳參考值呼叫(pass by value of reference)。

(9)如果您熟悉C++語言的話,就會知道,C++語言支援 傳參考呼叫,一個類似功能的程式碼如下,請注意其結 果,i與j的內容是被更動的。

7.7物件引數的傳參考值呼叫

101 1

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

/* 檔名:ch7_08.cpp 功能:傳參考呼叫 */

#include <iostream>

#include <stdlib.h>

using namespace std;

class CVector2 //二維向量類別 {

public: int x,y; //二維向量的兩項元素資料

public: void set(int m,int n) //用於設定二維向量的兩項元素資料 {

x=m;

y=n;

} };

class CMyClass {

public: void swap(CVector2 &a,CVector2 &b) //互換函式,傳參考呼叫 {

CVector2 temp;

temp=a;

a=b;

b=temp;

} };

7.7物件引數的傳參考值呼叫

‐‐‐‐‐原始‐‐‐‐‐‐

Vector i=(20,40) Vector j=(15,45)

‐‐‐‐‐swap後‐‐‐‐‐‐‐

Vector i=(15,45) Vector j=(20,40)

執行結果

(10)不只C++支援傳參考呼叫,C#同樣也支援傳參考呼叫,上述範 例改寫為C#版本的程式碼如下,請注意其結果,i與j的內容是被 更動的。

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

int main(char args[]) {

CVector2 i;

i.set(20,40);

CVector2 j;

j.set(15,45);

cout << "---原始---" << endl; //列印

cout << "Vector i=(" << i.x << "," << i.y << ")" << endl;

cout << "Vector j=(" << j.x << "," << j.y << ")" << endl;

CMyClass X;

X.swap(i,j); //互換i,j

cout << "---swap後---" << endl; //列印

cout << "Vector i=(" << i.x << "," << i.y << ")" << endl;

cout << "Vector j=(" << j.x << "," << j.y << ")" << endl;

system("pause");

return 0;

}

7.7物件引數的傳參考值呼叫

103

7.7物件引數的傳參考值呼叫

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

//檔名:ch7_08.cs 功能:傳參考呼叫 using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace ConsoleApplication1 {

class Program {

static void Main(string[] args) {

CVector2 i = new CVector2();

i.set(20,40);

CVector2 j = new CVector2();

j.set(15,45);

Console.WriteLine("---原始---");

Console.WriteLine("Vector i=(" + i.x + "," + i.y + ")");

Console.WriteLine("Vector j=(" + j.x + "," + j.y + ")");

CMyClass X = new CMyClass();

X.swap(ref i, ref j); //傳參考呼叫 Console.WriteLine("---swap後---");

Console.WriteLine("Vector i=(" + i.x + "," + i.y + ")");

Console.WriteLine("Vector j=(" + j.x + "," + j.y + ")");

Console.Read();

} }

‐‐‐‐‐原始‐‐‐‐‐‐

Vector i=(20,40) Vector j=(15,45)

‐‐‐‐‐swap後‐‐‐‐‐‐‐

Vector i=(15,45) Vector j=(20,40)

執行結果

7.7物件引數的傳參考值呼叫

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

class CMyClass {

public void swap(ref CVector2 a, ref CVector2 b) //傳參考呼叫 {

CVector2 temp;

temp = a;

a = b;

b = temp;

} }

class CVector2 //二維向量類別 {

public int x, y; //二維向量的兩項元素資料

public void set(int m, int n) //用於設定二維向量的兩項元素資料 {

x = m;

y = n;

} } }

105

this是一個特殊的關鍵字,在撰寫程式 時代表類別的本身,在實際執行時,則 代表物件的本身。

不過我們先暫時不討論this關鍵字,而是思考另 外一個問題,即『當成員變數與區域變數同名』

的問題。

相關文件