昨天學習了 Constructor 建構子,讓我們可以在建立 Expense 物件的同時,直接放入消費項目、金額等資料。
目前的 Expense 類別大概是這樣:
class Expense {
String item;
int amount;
String category;
Expense(String item, int amount, String category) {
this.item = item;
this.amount = amount;
this.category = category;
}
}
雖然已經可以使用,但這些資料可以直接從外部修改。
例如:
expense.amount = -500;
這樣就可能出現不合理的消費金額。
所以今天要學 Java 物件導向的另一個重要觀念:Encapsulation(封裝)。
什麼是 Encapsulation?
封裝可以簡單理解成:
把物件內部的資料保護起來,不讓外部程式隨便修改。
在 Java 中,可以使用 private:
private String item;
private int amount;
private String category;
當變數設定成 private 之後,就不能直接在其他地方寫:
expense.amount = 100;
而是要透過指定的方法來取得或修改資料。
private 是什麼?
之前我們寫:
String item;
int amount;
今天改成:
private String item;
private int amount;
private 代表這些變數主要只能在自己的 Class 裡直接使用。
例如:
class Expense {
private String item;
private int amount;
}
如果 Main 想直接使用:
expense.item
就會出現錯誤。
因此我們需要另外建立方法,讓外部程式可以安全地取得資料。
Getter:取得資料
Getter 可以用來取得物件裡面的資料。
例如:
public String getItem() {
return item;
}
以及:
public int getAmount() {
return amount;
}
之後就可以使用:
expense.getItem();
expense.getAmount();
例如:
System.out.println(expense.getItem());
就可以取得消費項目。
Setter:修改資料
如果希望允許修改資料,可以建立 Setter。
例如:
public void setItem(String item) {
this.item = item;
}
使用:
expense.setItem("晚餐");
就可以修改消費項目。
金額也可以建立:
public void setAmount(int amount) {
this.amount = amount;
}
不過 Setter 還有一個很實用的地方,就是可以加入條件判斷。
例如:
public void setAmount(int amount) {
if (amount >= 0) {
this.amount = amount;
} else {
System.out.println("金額不能小於 0!");
}
}
這樣如果有人輸入:
expense.setAmount(-500);
程式就不會直接接受這個不合理的金額。
這也是封裝的好處之一。
今天的小實作:封裝 Expense
今天把之前的 Expense 修改成:
class Expense {
private String item;
private int amount;
private String category;
Expense(String item, int amount, String category) {
this.item = item;
this.amount = amount;
this.category = category;
}
public String getItem() {
return item;
}
public int getAmount() {
return amount;
}
public String getCategory() {
return category;
}
public void setItem(String item) {
this.item = item;
}
public void setAmount(int amount) {
if (amount >= 0) {
this.amount = amount;
} else {
System.out.println("金額不能小於 0!");
}
}
public void setCategory(String category) {
this.category = category;
}
}
現在 item、amount 和 category 都變成 private。
修改原本的記帳系統
因為變數變成 private,所以之前這種寫法:
expense.item
expense.amount
就不能再使用。
要改成:
expense.getItem()
expense.getAmount()
完整程式可以整理成:
import java.util.Scanner;
import java.util.ArrayList;
class Expense {
private String item;
private int amount;
private String category;
Expense(String item, int amount, String category) {
this.item = item;
this.amount = amount;
this.category = category;
}
public String getItem() {
return item;
}
public int getAmount() {
return amount;
}
public String getCategory() {
return category;
}
public void setItem(String item) {
this.item = item;
}
public void setAmount(int amount) {
if (amount >= 0) {
this.amount = amount;
} else {
System.out.println("金額不能小於 0!");
}
}
public void setCategory(String category) {
this.category = category;
}
}
public class Main {
static Scanner scanner = new Scanner(System.in);
static ArrayList expenses = new ArrayList<>();
public static void main(String[] args) {
boolean running = true;
while (running) {
showMenu();
System.out.print("請選擇功能:");
int choice = scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
addExpense();
break;
case 2:
showExpenses();
break;
case 3:
running = false;
System.out.println("已離開個人記帳系統!");
break;
default:
System.out.println("輸入錯誤,請重新選擇!");
}
}
scanner.close();
}
public static void showMenu() {
System.out.println();
System.out.println("===== 個人記帳系統 =====");
System.out.println("1. 新增消費");
System.out.println("2. 查看紀錄");
System.out.println("3. 離開");
}
public static void addExpense() {
System.out.print("請輸入消費項目:");
String item = scanner.nextLine();
System.out.print("請輸入消費金額:");
int amount = scanner.nextInt();
scanner.nextLine();
System.out.print("請輸入消費分類:");
String category = scanner.nextLine();
Expense expense =
new Expense(item, amount, category);
expenses.add(expense);
System.out.println("記帳成功!");
}
public static void showExpenses() {
System.out.println();
System.out.println("===== 消費紀錄 =====");
if (expenses.size() == 0) {
System.out.println("目前沒有消費紀錄。");
} else {
for (int i = 0; i < expenses.size(); i++) {
Expense expense = expenses.get(i);
System.out.println(
(i + 1) + ". "
+ expense.getItem()
+ "|" + expense.getAmount() + " 元"
+ "|" + expense.getCategory()
);
}
}
}
}
實際執行看看
新增一筆消費:
===== 個人記帳系統 =====
再新增一筆:
請輸入消費項目:捷運
請輸入消費金額:30
請輸入消費分類:交通
記帳成功!
查看紀錄:
===== 消費紀錄 =====
雖然使用者看到的結果沒有太大變化,但是程式內部的資料已經受到保護。
Getter 和 Setter 怎麼記?
今天一次出現很多新的名稱,我覺得可以用最簡單的方法記。
Getter = Get = 取得資料
expense.getAmount();
意思就是:
我要取得這筆消費的金額。
Setter = Set = 設定或修改資料
expense.setAmount(200);
意思就是:
我要把這筆消費的金額設定成 200。
而:
private int amount;
則代表:
不希望外面的程式直接亂改 amount。
所以整體可以理解成:
private
↓
保護資料
↓
Getter → 讀取資料
Setter → 修改資料
Day 13 學習心得
今天學習了 Java 物件導向中的 Encapsulation 封裝。一開始看到 private、Getter 和 Setter 覺得程式碼反而變多了,但實際理解之後,發現封裝的目的不是單純讓程式變複雜,而是讓資料的存取方式更有規則。
把消費項目、金額和分類設定成 private 之後,外部程式不能直接修改,而是需要透過 Getter 和 Setter 操作。Setter 裡還可以加入條件判斷,避免不合理的資料被存進物件。
目前從 Class、Object、Constructor 到 Encapsulation,我也開始比較能理解 Java 為什麼會強調物件導向。