「將某 class 產生出一個 instance 之後,此 class 所有的 instance field 都會新增一份,那麼所有的 instance method 是否也會新增一份?」我常常看到網路上有人在討論此問題,所以寫了這篇文章,以為解釋。
Member 的種類
類別(class)的內部組成統稱為成員(member),如果依據成員是「資料」還是「程式」來區分,可以分成:
資料,被稱為 field
程式,被稱為 method
如果再依據有無使用 static 修飾而細分,則成員可細分成四種:
資料,被稱為 field
程式,被稱為 method
如果再依據有無使用 static 修飾而細分,則成員可細分成四種:
- class field:有用 static 修飾的 field
- class method:有用 static 修飾的 method
- instance field:沒有用 static 修飾的 field
- instance method:沒有用 static 修飾的 method
顧名思義,class field/method 和 class 本身有密切的關係,而 instance field/method 則與 instance(也就是物件)有密切的關係。請看下面的範例程式。
public class Class1 { public static int classField; public static void classMethod1() { // ... } public static void classMethod2() { // ... } public int instanceField; public void instanceMethod1() { // ... } public void instanceMethod2() { // ... } } public class Class2 { public static void classMethod () { // ... } public void instanceMethod() { // ... } }
Field
宣告 field 時,如果前面加上 static 的修飾字,就會使得此 field 變成是 class field。一個 class field 永遠只佔用一塊記憶體,而且此記憶體空間是在此 class 一被載入(load)記憶體之後就立刻配置的(allocate),感覺就如同此 field 與該 class 本身相關,而不是與該 class 的 instance 相關。class field 可以被同一個 class 的 class method 內部所直接使用,舉例來說,上述的 classMethod1() 內部可以出現 classField。如果 Class1 的 class method 或 instance method 欲使用到 Class2 的 class field,就必須在前面冠上 Class2,例如:Class2.classField。
宣告field時,如果前面「不」加上 static 的修飾字,就會使得此 field 變成是 instance field。對 instance field 而言,每產生一個instance(物件)就多一塊 instance field 的記憶體,每少一個 instance 就少一塊 instance field 的記憶體。instance field 可以被同一個 instance的 instance method 內部所直接使用,舉例來說,上述的 instanceMethod1() 內部可以出現 instanceField。如果某 class 的class method 或 instance method 欲使用到某 instance 的 instance field,就必須在前面冠上 instance 名稱,例如:obj.classField。
Method
宣告 method 時,如果前面加上 static 的修飾字,就會使得此 method 變成是 class method。對 class method 而言,永遠只佔用一塊記憶體,而且此記憶體空間是在此 class 一被載入進記憶體之後就立刻配置的,就如同此 method 是與該 class 本身相關,而不是與該 class 的 instance 相關。class method 可以被同一個 class 的任何 class method 內部所直接使用,舉例來說,上述的classMethod1() 內部可以出現 classMethod2()。如果 Class1 的 class method 或 instance method 欲使用到 Class2 的 classMethod(),就必須在前面冠上 Class2,也就是 Class2.classMethod()。
宣告 method 時,如果前面「不」加上 static 的修飾字,就會使得此 method 變成是 instance method。對 instance method 而言,每產生一個 instance「並不會」多一塊 instance method 的記憶體。同一個 method 不管被調用(invoke)幾次,也不管被調用時的instance 是何者,每次的程式碼完全都一樣,差別只在每次執行時資料不同,而資料是存放在 call stack 中,所以不會混淆。在 instance method 內,資料的來源包括了參數和 instance field。參數被傳進來變成 call stack 內的 entry,所以不會混淆,這很容易理解,但是 instance field 是如何區隔開來的(前面剛提過:instance field 會隨著 instance 數目增加而增加),這是透過隱匿(implicit)的 this 參數來達到了,稍後會有說明。instance method 可以被同一個 instance 的 instance method 內部所直接使用,舉例來說,上述的 instanceMethod1() 內部可以出現 instanceMethod2()。如果某 class 的 class method 或 instance method 欲使用到某 instance 的某 instance method,就必須在前面冠上此 instance 名稱,例如:obj.classMethod()。
隱匿的 this 參數
綜合上面的敘述來看:
- class field:共用一塊記憶體
- class method:共用一塊記憶體
- instance field:隨著每個 instance 各有一塊記憶體
- instance method:共用一塊記憶體
instance method 為什麼不是隨著每個 instance 佔有一塊記憶體,反倒是共用一塊記憶體?其實,讓每個 instance method 如同instance field 一樣,隨著每個 instance 佔有一塊記憶體,這麼做當然是可以的,只是 Java 編譯器和 JVM 都不這麼做,因為太浪費記憶體空間了。一個 field 少則佔用一個 byte,多則佔用數百 Byte,但是 method 少則數個 byte,多則數百 Kilo Byte。Mehtod耗費的記憶體是 field 的數百倍,甚至數千倍,當然是能共用就盡量共用,比較不會消耗記憶體。既然 JVM 讓一個 class 的所有instance 共用相同的 instance method,下面兩行程式碼在 instanceMethod() 內部時,如何區分是 instance1 或 instance2?
instance1.instanceMethod();
instance2.instanceMethod();
因為編譯器會幫我們在把 instance1 和 instance2 個別傳入 instanceMethod() 中當作第一個參數。也就是說,任何 instance method 參數的實際個數都會比表面上多一個,這個多出來的參數是由 Java 編譯器幫我們加上去的,用來代表對應的 instance。此參數的變數名稱為 this,也是 Java 的一個關鍵字(keyword)。
當調用某個 instance method 或使用某個 instance field 時,你必須在前面加上該 instance 的名稱,如果該 instance method/field 相關的 instance 和當時程式碼所在的 instance method 的 instance 指的是同一個 instance 時,該 instance 的名稱就是 this,這種情況下,你也可以選擇不在前面加上「this.」。
然而,在某些狀況下,非得在前面加上「this.」不可。例如,當method中的參數或區域變數和 instance field 名稱完全相同時,如果不在前面冠上「this.」,那麼指的是參數或區域變數;如果在前面冠上「this.」,那麼指的才是 instance field。
本文作者:蔡學鏞
張貼日期:09/09/2002
參考自[Java] Static 的意義與實作方式