# 什麼是"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的方法,則編譯時就會出現錯誤。