類別及封裝

      在〈類別及封裝〉中尚無留言

類別Class

在Java進階的說明中, 可以很明確的說, 類別其實就是一種自訂的資料型態, 跟 C 的 struct一樣. 不過如果這麼看待 class的話, 就太小覷它了.

class是由 struct演化而來沒錯, 但後續在裏面加入了方法, 權限, 繼承, 抽象類別, 介面等等功能, class 因此而偉大複雜.

在class內, 方法外, 也就是下圖紅色框線的地方, 一般又稱為 Field區, 存放者物件變數或類別變數. 類別變數前面會加static , 物件變數則又稱為非static 變數.

java_class1

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” , 中間要加 “.”

java_class2

封裝

上述的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

private 修飾子

為了解決上述容易被修改成不正常的神奇寶貝, 就要將其物件變數改為private, 將之封印起來, 不讓玩家可以隨便更改. 等到需要存取時, 再用public方法將之解開封印

以上可知, 重要之類別變數設為public, 會造成危險.  當然, 方法亦可宣告為public or private, 其用意同上.

 Set & Get 方法

使用setter/getter存取子已成為物件導向封裝的習慣. 因為可以在setter方法中檢查設定的值是否正確

static 類別變數

加上static的變數, 稱為類別變數, 此變數屬於類別所擁有. 在如下的程式碼中, p1.level, p2.levle皆為獨立的. 但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

java_static

在上面的圖示中, static int count並不會下降到物件中,  而是存於類別之中的.

存取問題

嚴格說, p1.count其實的是錯誤的. count是大家共有的, 憑什麼說count是p1的. 這在物件導向的設計上是有缺失的.

正確的存取方式, 是要透過類別去存取, 如 Pokemon.count.

請注意, 在 C#中, 使用 p1.count是被禁止的

生命周期

太陽已存在了46億年. 而人類頂多也才出現不超過十萬年. 所以太陽不是為了人類而掛在天空的.

static 變數就像太陽一樣, 打從程式載入時期, 就存在著. 而程式的載入期到程式的執行時期, 差了數百萬奈秒.

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

public static final POWER_SUSPEND=2; 這一行只是限定這個變數為2, 且不能改變其值
Computer comp=new Computer();
comp.setState(Computer.POWER_SUSPEND);這樣可以編譯, 但
comp.setState(42);一樣可以編譯, 所以需在setState()裏還是要檢查傳入的值是否在範圍之內

列舉(Enumerations)

列舉用來定義新型的資料型別, 實際上就是一種類別, 可單獨撰寫, 也可以當成內部類別

enum PowerState{
    OFF,
    ON,
    SUSUPEND;
}

使用方式

comp.setState(PowerState.OFF); <==直接用OFF, 不是轉成數字
private PowerState powerState=ON;

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 or 預設, 因為列舉不能new 出新物件

enum只有當成內部類別才可以加上static, 不過編譯器會自動幫內部類別加上static final.
若是當成外部類別, 而且有加上public的話, 則檔名一樣要跟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.values(); 則week陣列就會取得從Sunday到Saturday等7個值

使用valueOf(String)

Week week=Week.valueOf(“Sunday”);

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *