類別Class
在 Java 進階的說明,可以很明確的說類別其實就是一種自訂的資料型態,跟 C 的 struct 一樣。不過如果這麼看待 class的話,就太小覷它了。
class 是由 struct 演化而來,但接續在裏面加入了方法、權限、繼承、抽象類別、介面等等功能,class 因此強大且複雜。
在 class 內,方法外,也就是下圖紅色框線的地方,一般又稱為 Field區,存放者物件變數或類別變數。類別變數前面會加 static,物件變數則又稱為非 static 變數。
New
產生一個物件, 又稱為實例化(Instance)。假如有一代碼如下
class Pokemon{ int level; double w; Pokemon next; }
當下達 new Pokemon(),其實就是跟系統下達 malloc(16) 取得 16byte 的空間,其中 4byte規 畫給level, 8byte 給 w,最後是 4 byte 給 next 存放物件的 hashcode。
所以當 new 3 次時,就會如下圖所示,產生 3 個物件,每個物件都會各自獨立的 level,w,及 next。class好比是一個模具, 使用此模具製作出三個完成品。
存取物件 1 的 level,就要使用 “物件1.level” ,中間要加 “.”
封裝
上述的 class Pokemon{},需想像是任天堂這間公司開發的。而 main() 這部份是另一間軟体開發商所開發的。
軟体開發商通常都會跟任天堂,Facebook,Line 等公司簽定 NDA (保密協定)並支付龐大的簽約金才可獲取相關技術及 SDK(Software Development Kit)。所以 class Pokemon 就是 SDK的一部份。
軟体開發商依 SDK 寫了 Pokemon p1=new Pokemon(),此時可以直接使用 p1.level=5000,這隻神奇寶貝的等級一下子就拉高變成了超級塞亞人,這不就違反了等級需要一步一步訓練而來的規定嗎。
任天堂為了防止軟体開發商使用此招,就會把 level 加上 private,即
class Pokemon{
private int level;
}
將物件變數使用 private 保護起來,如此軟体開發商就無法使 p1.level=5000。
使用 private 保護的物件變數,對軟体開發商而言尤如垃圾一樣,無法使用。所以通常會再加入 setter/getter存取子。
class Pokemon{ private int level; double w; public void setLevel(int l){ if (l>100)this.level=1; else this.level=l; } public int getLevel(){ return level; } }
public 修飾子
標示為public的物件變數,意味著可以在 main 裏 new 出物件後,直接由物件去修改其值。若沒加 public,也沒加 private,稱為預設權限。預設權限在相同 package 裏如同public,在不同 package 如同 private。
private 修飾子
為了解決上述容易被修改成不正常的神奇寶貝,就要將其物件變數改為 private,將之封印起來,不讓玩家可以隨便更改,等到需要存取時,再用 public 方法將之解開封印。
以上可知,重要之類別變數設為 public,會造成危險。方法亦可宣告為 public 或 private,其用意同上。
Set & Get 方法
使用 setter/getter 存取子已成為物件導向封裝的習慣,因為可以在 setter 方法中檢查設定的值是否正確。
static 類別變數
加上 static 的變數稱為類別變數,此變數屬於類別所擁有。在如下的程式碼中,p1.level,p2.levle 皆為獨立的。但 count 變數不同,p1.count 一更改後,p2.count 也一併跟著更改。
public class Test { public static void main(String[] args){ Pokemon p1=new Pokemon(); Pokemon p2=new Pokemon(); p1.count=100; p1.level=10; p2.count=200; p2.level=20; System.out.printf("p1.level=%d, p1.count=%d\n", p1.level, p1.count); System.out.printf("p2.level=%d, p2.count=%d\n", p2.level, p2.count); } } class Pokemon{ static int count; int level; } 結果 : p1.level=10, p1.count=200 p2.level=20, p2.count=200
在上面的圖示中,static int count 並不會下降到物件中,而是存於類別之中的。
存取類別變數
count 是類別變數,要存取類別變數的方法必需是 Pokemon.count。
雖說 p1.count 也可以存取,但這是物件導向設計上的缺失。嚴格說, p1.count 其實的是錯誤的。因為 count 是大家所共同擁有,憑什麼說 count 是 p1的。但這個 bug 已錯了 30 幾年了,若立即更正會造成早期寫出來的程式不相容,所以一直沒修正。所以請盡量不要使用 p1.count,否則會造成日後程式碼維護困難。
請注意在 C# 中使用 p1.count 是被禁止的。
生命周期
太陽已存在了46億年,而人類頂多也才出現不超過十萬年。所以太陽不是為了人類而掛在天空的。
static 變數就像太陽一樣,打從程式載入時期,就存在 Ram 中。而程式的載入期到程式的執行時期,差了數千百萬奈秒。
static 方法不能存取非 static 方法及變數。意思是 static 方法早就存在了,而非 static 方法及變數要到執行時期才可能會出現。所以叫 static 去存取尚未存在的東西,是不可能的事。
static 方法
又稱類別方法,比如 java.lang.Math 就包含了很多的 static 方法。static 方法可以在子類別中隱藏,但不是覆蓋喔。
呼叫 static 方法時,也是直接使用類別名稱,如Math.random()。
static import
import static java.lang.Math.random;
有了如上宣告,就可以直接使用double d=random();
import static java.lang.System.*;
則可以直接使用out.println();
永久不變的變數
變數宣告為 private 時,可以使用 public 方法改變其值。但若想建立一個永不能變的變數時,比如帳號號碼, 就必需把public setter 的方法拿掉。但一拿掉,那如何初使化帳號號碼值呢? 解決方法就是使用建構子,在new 物件時,立即初使化值。
class Account{ private int accountNumber; public Account(int number){ accountNumber=number; } }
final
final 方法
方法可以被宣告為 final,如
public final void printMessage(){}
將方法宣告為 final 的目的,就是禁止被子類別覆蓋。
final 類別
class 也可以宣告為 final,其目的就是禁止被繼承。
final 變數
final 變數在初始化後,就不能改變其值。final 變數可以加在類別欄位,方法的參數,區域變數三個地方。
加在類別欄位中,即為常數的意思,請特別注意 : 類別欄位加上 final 時,如果在宣告時沒有初始化,就一定要在建構子中初始化,否則會編譯錯誤。
static 變數也可以加 final,但一宣告就一定要初始化,通常會用大寫或底線。
何時要避免Constants
底下的代碼中,POWER_SUSPEND 限定這個變數為 2,且不能改變其值
class Computer{ public static final POWER_SUSPEND=2; public void setState(int x){ .............. } } class Main{ public static void main(String[] args){ Computer comp=new Computer(); comp.setState(Computer.POWER_SUSPEND); comp.setState(100); } }
上面代碼中,comp.setState(Computer.POWER_SUSPEND) 可以編譯。
而 comp.setState(42) 一樣可以編譯,所以需在 setState() 裏還是要檢查傳入的值是否在範圍之內。
列舉(Enumerations)
列舉用來定義新型的資料型別,實際上就是一種類別,可單獨撰寫,也可以當成內部類別。
enum PowerState{ OFF, ON, SUSUPEND; }
使用方式
comp.setState(PowerState.OFF); <==直接用OFF,不是轉成數字。
public void setState(PowerState state){ switch(state){ case OFF:... } }
複雜的列舉
列舉可以有欄位,方法及 private 建構子
enum PowerState{
OFF("The power is off"), //呼叫建構子, 初始化public static OFF 參考
ON("The power is on"),
SUSUPEND("The power usage is low");
private String description;
private PowerState(String d){
description=d;
}
public String getDescription(){
return description;
}
}
public class Test{
public static void main(String[] args) {
System.out.println(PowerState.OFF);
PowerState s=PowerState.ON;
System.out.println(s.getDescription());
}
}
列舉的建構子只能是 private 或預設,因為列舉不能 new 出新物件。
enum 只有當成內部類別才可以加上 static,不過編譯器會自動幫內部類別加上 static final。若是當成外部類別,而且有加上 public 的話,則檔名一樣要跟 enum 類別名相同。
假設有一個 enum 如下
enum Week{ Sunday, Monday, Tuesday, Wednesday, Thrusday, Friday, Saturday; }
若為外部類別,則會編譯出Week.class,若是內部類別,則會編譯成Test$Week.class。列舉的 class 都是 final 的,裏面的值全都是 public static final,如下
public static final Week Sunday; public static final Week Monday;
取得單一值
Week week=Week.Sunday
取得全部的值
底下的代碼可以取得 Week 裏所有的值,產生 week陣列。
Week[] week=Week.values();
使用valueOf(String)
Week week=Week.valueOf(“Sunday”);