# 什麼是"this"
this關鍵字:
this在英文的意思是「這個、這裡」,不過在程式語言中,要把它記為「這個類別的…」或「這個物件的…」。打個比喻,你要送包裹給大樓的王先生,但如果同一棟大樓裡有很多個王先生,你就必須和大樓管理員說明:「是五樓之三的那個王先生」才能順利把貨送到他手上。
意思就是,程式雖然不容許變數在同一個{ }區塊之內「撞名」(即變數的名稱完全相同),但如果同名的變數位於不同的{ }區塊,程式仍可區分出來,語法上仍是合理的,這就是區域變數(local variable)和全域變數(global variable)的觀念。不過,程式有條預設規則:「當敘述所在之處有相同名稱的全域變數和區域變數時,則該敘述優先使用區域變數」,這就會造成我們無法順利的將資料傳遞到我們想要的地方。
註:回顧個基本觀念,絕大部分的程式語言都會區分大小寫,故apple和Apple是不同的名稱。
來做個示範,以下故意讓類別的屬性(成員變數)和建構子的引數撞名:
```
class Student { // 宣告一個Student類別
// 以下定義Student類別的屬性(property)
public String studentID ; // 宣告字串型態的學生證號碼
public String name ; // 宣告字串型態的名字
public int height ; // 宣告整數型態的身高
// 以下定義Student類別可以使用的方法(method)
// 先定義Student類別的建構子(constructor)
public Student(String studentID , String name , int height) // 定義建構子和它的引數
{
studentID = studentID ; // 特別注意這邊!
name = name ; // 特別注意這邊!
height = height ; // 特別注意這邊!
}
public String say() // 定義say()方法
{ System.out.println(”你好嗎 “); }
public int walk() // 定義walk()方法
{ … … … }
}
```
注意上面的屬性(成員變數)標示不同底色的部分,並注意以上程式碼中間三行標示要特別注意的地方。雖然現在我們「希望」建構子把它的引數(等號右邊)傳遞到傳遞Student類別的屬性中(等號左邊…至少我們現在是這樣想),但現在由於類別的屬性和建構子的引數撞名了,所以編譯器無法分辨到底哪個是哪個,就會依照它預設的規則行事,即我們剛才所說的:「當敘述所在之處有相同名稱的全域變數和區域變數時,則該敘述優先使用區域變數」。所以以上的寫法,等於只是把資料又丟回給建構子自己,而沒有順利的把資料傳遞出去,是沒有意義的行為。
為了解決這個問題,我們使用「this關鍵字」改寫以上程式:
```
class Student { // 宣告一個Student類別
// 以下定義Student類別的屬性(property)
public String studentID ; // 宣告字串型態的學生證號碼
public String name ; // 宣告字串型態的名字
public int height ; // 宣告整數型態的身高
// 以下定義Student類別可以使用的方法(method)
// 先定義Student類別的建構子(constructor)
public Student(String studentID , String name, int height) // 定義建構子和它的引數
{
this .studentID = studentID ; // 把建構子的引數,傳遞到Student類別的屬性中
this .name = name ; // 把建構子的引數,傳遞到Student類別的屬性中
this .height = height ; // 把建構子的引數,傳遞到Student類別的屬性中
}
public String say() // 定義say()方法
{ System.out.println(”你好嗎 “); }
public int walk() // 定義walk()方法
{ … … … }
}
```
注意上面的屬性(成員變數)標示不同底色的部分。使用this關鍵字之後,我們就可以順利的把建構子的引數,傳遞到Student類別的屬性中了。
接續第二段的程式碼,我們用Student類別來創造學生物件:
Student 阿輝 = new Student(MA6961475, 阿輝 , 183); // 在建構子的()內傳入引數
則會創造一個名為阿輝的學生物件,他的學號為AM6961475,名字叫阿輝,身高為183,於是我們順利完成了從類別創造出物件的動作,並同時做了該物件的初始化。
由此可見,為了方便記憶,我們可以把「this .」的語法記憶為「這個類別的…」或「這個物件的…」,也就是說將「this .studentID = studentID ;」翻譯成中文就是:「把等號右側studentID的值,傳遞到「這個類別的」studentID屬性中」,其餘依此類推。
雖然此例是以屬性(成員變數)和建構子做示範,但方法(成員函數)的模式也相同。
如果我們都以不同的名稱做出區隔,就不需要加上「this .」。不過在實際撰寫程式時,為了這個目的還要特意去想許多不同的名稱有點麻煩,而且許多不同但相似的變數名稱變多了的話,程式的可讀性和維護性也會變差,所以學會「this .」這個語法非常重要。
-----------------------------------------------------------------------------------------
前面介紹了使用「this .屬性」或「this.方法」,以及「super.屬性」或「super.方法」的使用方式,接著再介紹第三種方式,即使用「this(參數)」或「super(參數)」的方式來呼叫(執行)建構子。
使用方式如下:
this():執行同一類別內的「其他」建構子。
super():從子類別執行其「父類別」的建構子。
我們知道建構子可以多載(overload),當建構子有多載時,this()和super()都會根據()內參數的個數、型態或順序,來判斷要呼叫(執行)哪一個建構子。
程式碼範例1:
```
class Car { // 宣告一個Car類別
public Car(){ // 定義Car類別的建構子(無參數的版本)
System.out.println("無參數的建構子"); // 印出文字
}
public Car(int n){ // 定義Car類別的建構子(一個參數的版本)
this(); // 呼叫同一類別內的「其他」建構子(此處為無參數的版本)
System.out.println("有一個參數的建構子"); // 印出文字
}
public Car(int n ,double d){ // 定義Car類別的建構子(兩個參數的版本)
this(63); // 呼叫同一類別內的「其他」建構子(此處為一個參數的版本)
System.out.println("有兩個參數的建構子"); // 印出文字
}
} // Car類別的定義結束
public class Sample { // 宣告一個Sample類別
public static void main(String[ ] args) // 程式的進入點
Car car1 = new Car(67); // 使用一個參數的建構子,實體化建立一個car1物件
System.out.println(\n); // 單純空出一列(排版而已)
Car car2 = new Car(123 , 25.2471); // 使用兩個參數的建構子,實體化建立一個car2物件
}
```
執行結果如下:
無參數的建構子
有一個參數的建構子
(空一列)
無參數的建構子
有一個參數的建構子
有兩個參數的建構子
此段程式碼同時也應用了「建構子多載」的觀念,即程式會自動依據建構子的()所傳入的參數的個數(或形態、順序)來自動判斷要呼叫(執行)哪一個建構子。 -----------------------------------------------------------------------------------------
相較於this()是呼叫同一個類別的「其他」建構子,super()則是從子類別中呼叫其「父類別」的建構子,並且也同樣支援多載:
程式碼範例2:
```
class Car{ // 宣告一個Car類別
public Car(){ // 定義Car類別的建構子(無參數的版本)
System.out.println("父類別中無參數的建構子"); // 印出文字
}
public Car(int n){ // 定義Car類別的建構子(一個參數的版本)
this();
System.out.println("父類別中有一個參數的建構子"); // 印出文字
}
public Car(int n ,double d){ // 定義Car類別的建構子(兩個參數的版本)
this(63);
System.out.println("父類別中有兩個參數的建構子"); // 印出文字
}
class RacingCar extends Car{ // 定義子類別RacingCar繼承自父類別Car
public RacingCar(int n){ // 定義RacingCar類別的建構子(一個參數的版本)
super(24); } // 在建構子的定義中呼叫父類別(一個參數的)建構子
}
public class Sample { // 宣告一個Sample類別
public static void main(String[ ] args) { // 程式的進入點
RacingCar car3 = new RacingCar(75); // 使用一個參數的建構子,實體化建立car3物件
}
}
```
由於RacingCar類別的父類別是Car類別,在定義RacingCar類別的建構子時,使用了super(24)呼叫它的父類別(Car類別)中一個參數版本的建構子,所以執行結果如下:
父類別中無參數的建構子
父類別中有一個參數的建構子
-----------------------------------------------------------------------------------------
總結:
1. 「this . 屬性」,可以存 / 取同一個類別中的屬性。
2. 「this . 方法」,可以呼叫(執行)同一個類別中的方法。
3. 「this()」,可以呼叫(執行)同一個類別中的建構子,支援多載(overload)。
4. 「super . 屬性」,可以從子類別中存 / 取它的父類別中的屬性。
5. 「super . 方法」,可以從子類別中呼叫(執行)它的父類別中的方法。
6. 「super()」,可以從子類別中呼叫(執行)它的父類別中的建構子,支援多載(overload)。
要特別注意的是,this()和super(),依照「規定」(就是規定,沒什麼好說的),都必須寫在該類別建構子定義的第一行,而且同一個建構子內不能同時存在this()和super()。
還有一點,就是this和 super在有static類別、static屬性或static方法的場合下,無法使用。
_______________________________________
## 補充說明:
當物件呼叫方法時,它們會呼叫共用的方法成員,但方法成員中如果有物件的資料成員的話,如何確認是哪一個物件所擁有的呢?答案是物件會告訴方法一個 this 參考名稱。
每一次物件呼叫共用的方法時,隱含的都會傳遞給方法一個this 參考名稱,這個名稱會參考至物件本身,舉個例子來說,程式碼 11-1 的 hello()方法片段:
假設 hello1 呼叫 hello()方法,則 this 所參考的物件就是 hello1 所參考的物件,因而知道所使用的 name 成員就是 hello1 所參考物件的 name 成員;同樣的,如果 hello2 呼叫 hello() 方法,this 所參考的物件就是 hello2 所參考的物件,所以也可以正確的存取到 hello2 的 name 成員。
藉由 this 正確存取「物件」的資料成員
必要的話,您可以明確在程式中撰寫 this,這表示您要使用的是物件自己的成員,例如當類別變數名稱與方法上的參數名稱相同時,為了避免參數的作用範圍覆蓋了類別變數的作用範圍,您要明確指定 this,直接改寫一下程式碼 11-1 為程式碼 11-6 作為示範。
public class Hello2{
private String name;
public Hello2(){
name = "nobody";
}
public Hello2(String name){
this.name = name;
}
public void hello(){
System.out.print("Hello ");
System.out.println(this.name);
}
public void setName(String name){
this.name = name;
}
}
在程式碼的第 8 行到第 10 行,由於參數名稱為類別變數名稱都是 name,為了區別兩者,在類別變數名稱上明確指定this 參考,表示這個 name 會是物件自己的資料成員,藉此與參數名稱作區別,第 17 行到第 19 行也是相同的作用。
this 可以有一種帶參數的呼叫方法,作用為呼叫類別中的建構子,例如:
public class Hello3{
private String name;
public Hello3(){
this("nobody");
}
public Hello3(String name){
this.name = name;
}
public void hello(){
System.out.print("Hello ");
System.out.println(this.name);
}
public void setName(String name){
this.name = name;
}
}
當使用預設建構式時,this("nobody")將呼叫對應的參數建構式,也就是執行第 8 行至第 10 行。
由於 static 成員是屬於類別所擁有,所以呼叫 static 的方法時,不會傳入 this 參考名稱,因而在 static 方法中不能有非static 的資料成員,因為沒有 this 名稱可以辨別到底是哪一個物件的資料成員,如果在static方法中嘗試存取非static的屬性或非static的方法,則編譯時就會出現錯誤。