Part 1 提到過程導向與物件導向比較,及類別、物件之間的關係,這篇會進一步探討:
- 如何定義類別、宣告物件
- 物件導向特性一:封裝
- 修飾子、靜態成員、方法重寫
- 物件在記憶體的配置方法
- 參考型別變數
前面提到,物件導向是一種程式設計方法論,是以定義類別、建立物件的方式,將程式加以組合,降低互相的耦合性,認為物件具有屬性與方法,且屬性的狀態能夠被改變。
當在定義類別名稱,通常採大寫字母字首命名,屬性跟方法也要一起定義進去;由於類別本身就是用來建構物件,Java 有預設的建構子,如果自己想要客製建構程序,就一併定義進去。
class Book {
// 屬性
String name;
int year;
// 建構子(系統預設定義,也能撰寫自定義屬性與邏輯條件,但一自己定義建構子,Java 就預設不做)
Book () {
}
// 建構子(可以客製帶入引數)
Book (String name, int year) {
this.name = name;
this.year = year;
}
// 方法
void printInfo() {
System.out.printf("Name: %s, published year: %d", name, year);
}
}
物件導向三大特性之一 — 封裝
當我們使用 new
關鍵字建立物件(new Book()
/new Book("name",2024)
),系統判斷帶入參數,選擇對應的建構子(Constructor)Book()
/Book(String name, int year)
初始化、建立物件,如此一來,想建立多個物件,就不用一個個指定屬性及方法。
建構子的命名與自己類別名稱相同,使用 new
建構當下,便會執行建構子;類別本身就有不具引數的預設建構子,預設建構子不寫任何內容,但可以重寫加入定義條件,也能撰寫帶引數的建構子,自定義初始屬性與邏輯。
修飾子
考慮安全性,物件的狀態不隨便被外部更動,所以在定義類別時,會透過權限的概念,只允許類別存取特定類別的資料、方法,保護建構好的物件不會被隨意修改,這就是封裝(Encapsulation)的概念,而其中是以存、取修飾子來實現。
查到很多種修飾子的中文說法,有前置修飾子、權限修飾子、控制修飾子等翻譯,而修飾子有 public、private、protected、default 四種。
修飾子 | 同檔類別讀取 | 同 package 類別讀取 | 不同 package 類別讀取 |
---|---|---|---|
public | O | O | O |
private | O | X | X |
protected | O | O | 僅子類別可 |
default | O | O | X |
備註:
- protected 修飾子標註屬性、方法可讓同檔案、繼承子類別存取。
- default 為預設修飾子,可省略不寫。
// public 類別
public class publicClass() {
public String attr1;
publicClass() {}
}
// default 類別 (可省略修飾子)
class defaultClass() {
public String attr1;
defaultClass() {}
}
getter & setter
適當的封裝可以保護物件細節操作,而外部類別需要物件的資料,只能以公開接入方法取得、編輯內部屬性,慣例上會以 get屬性名()
、set屬性名()
來命名。
class Book {
String name;
int year;
Book (String name, int year) {
this.name = name;
this.year = year;
}
// getter
public int getYear() {
return this.year;
}
// setter
public void setter(year) {
this.year = year;
}
// printInfo 慣例上用 toString 來寫
@Override
public void toString() {
System.out.printf("Name: %s, published year: %d", name, year);
}
}
@Override 重寫
原本的 printInfo()
在這裡改用 toString()
方法表達:
這裡要先帶到 所有類別都是繼承於 Object 這個類別 的觀念,toString()
來自於 Object 類別中的定義(java.lang.Object),預設 toString()
輸出是:
package.class@hashCode
由於產生的雜湊碼實用性不高,實務上會拿它重寫,印出方便辨識的物件屬性或其他資訊。
static 靜態成員
類別定義的屬性、方法依照目前範例,都在建立物件後才能呼叫,而靜態成員的存在,則不需建立就能用。
在型態前面加上 static,就表示靜態成員,當一個屬性或方法只要用了 public static 宣告,就可透過類別來呼叫,靜態類別在記憶體配置屬於 global(下面提到),所以每個靜態成員都是唯一的,且不能被覆寫。
public static void main(String[] args) {
...
}
這個方法異常熟悉對吧!
每個類別的 main 函式是類別建構的起點,在物件建立前就要執行,也就是為什麼 main 方法得是靜態成員。
Stack & Heap
討論變數時候提到,當宣告一個基本型態變數 & 指定常值,Java 會根據型別為它分配記憶體空間,詳細的說,是 JVM 在記憶體的 Stack 區域分配一塊空間給這個變數儲存常值。
int num = 10;
JVM 的記憶體配置空間分成 Global、Stack、Heap,其中 Global 放置全域資料,類別共用的靜態成員都儲存在這,類別一定義就建立,不因為產生新類別重複建立。
Stack 中文為堆疊,儲存基本型態變數。
Heap 中文為堆積,用來儲存參考型別物件變數。
參考型別變數
宣告物件是以參考位址的方式建立,JVM 先在 Stack 區域開一塊空間配置記憶位址給變數,new 建立物件時,根據位址再去 Heap 放物件本體。
討論型別時提到,基本型別包含 byte、short、int、long、float、double、boolean、char 八種。
至於參考型別,則是基本型別以外,又細分可以分四類:
- 類別:包含自定義類別(自己寫的)、官方類別(String、BigDecimal、Scanner等)、第三方開發類別
- 陣列(Array)
- 介面(interface,大小寫要注意)
- 列舉(enum)
參考型別屬於物件,同樣需要實例化才能使用,語法是
類別/介面名稱 自定義變數名稱 = new 類別物件();
public class DemoClass {
public static void main(String[] args) {
/* 通常參考型別變數需要傳入參數並使用構造函數(建構子)實例化
* 但 String 算是例外,可以直接指定變數,Java 會自動建立實例
*/
BigDecimal bd = new BigDecimal("1.0");
String str = new String("test"); String str = "test";
}
}
這篇記錄了類別如何定義、封裝、修飾子、靜態成員、方法重寫、記憶體儲存位置、參考型別變數,下篇會提到常見的類別們。