昨天 Day 26 完成了「關鍵字搜尋」,現在記帳系統已經可以用關鍵字快速找到相關的消費紀錄。
目前系統已經可以做到:
新增消費 ✓
查看紀錄 ✓
修改紀錄 ✓
刪除紀錄 ✓
自動儲存 ✓
檔案讀取 ✓
分類統計 ✓
預算提醒 ✓
日期查詢 ✓
關鍵字搜尋 ✓
今天想再增加一個「消費分析」功能。
假設目前有:
早餐|60 元
捷運|30 元
午餐|150 元
電影|300 元
咖啡|80 元
我希望程式可以自動告訴我:
===== 消費分析 =====
最高支出:
電影|300 元|娛樂
最低支出:
捷運|30 元|交通
所以 Day 27 要練習一個很重要的程式設計概念:找最大值與最小值。
第一個問題:怎麼找最大值?
假設有:
60
30
150
300
80
人一眼就可以看出:
最大值 = 300
但 Java 必須一個一個比較。
最簡單的方法,就是先假設第一筆資料是最大的:
Expense maxExpense =
expenses.get(0);
例如第一筆是:
早餐|60 元
目前:
maxExpense = 早餐|60 元
接著開始和其他資料比較。
使用 for 逐筆比較
可以從第二筆開始:
for (int i = 1;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
}
為什麼從:
i = 1
開始?
因為:
expenses.get(0)
已經被拿來當成第一個最大值了。
所以只需要從第二筆開始比較。
使用 if 判斷誰比較大
核心程式:
if (
expense.getAmount()
> maxExpense.getAmount()
) {
maxExpense = expense;
}
假設:
目前最大:早餐 60
下一筆:
捷運 30
比較:
30 > 60
→ false
所以最大值還是:
早餐 60
下一筆:
午餐 150
比較:
150 > 60
→ true
更新:
maxExpense = 午餐 150
下一筆:
電影 300
比較:
300 > 150
→ true
再次更新:
maxExpense = 電影 300
最後就找到最高支出。
最低支出也是同樣概念
先假設第一筆最小:
Expense minExpense =
expenses.get(0);
然後:
if (
expense.getAmount()
< minExpense.getAmount()
) {
minExpense = expense;
}
唯一差別就是:
找最大值 → >
找最小值 → <
其實可以一次找最大和最小
不需要跑兩次 for。
可以:
Expense maxExpense =
expenses.get(0);
Expense minExpense =
expenses.get(0);
for (int i = 1;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
if (
expense.getAmount()
> maxExpense.getAmount()
) {
maxExpense = expense;
}
if (
expense.getAmount()
< minExpense.getAmount()
) {
minExpense = expense;
}
}
一次檢查所有資料,就同時找到:
最高支出
最低支出
建立 analyzeExpenses()
今天新增:
public static void analyzeExpenses()
完整功能:
public static void analyzeExpenses() {
System.out.println();
System.out.println(
"===== 消費分析 ====="
);
if (expenses.size() == 0) {
System.out.println(
"目前沒有消費紀錄。"
);
return;
}
Expense maxExpense =
expenses.get(0);
Expense minExpense =
expenses.get(0);
for (int i = 1;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
if (
expense.getAmount()
> maxExpense.getAmount()
) {
maxExpense = expense;
}
if (
expense.getAmount()
< minExpense.getAmount()
) {
minExpense = expense;
}
}
System.out.println();
System.out.println(
"最高支出:"
);
System.out.println(
maxExpense.getDate()
+ "|"
+ maxExpense.getItem()
+ "|"
+ maxExpense.getAmount()
+ " 元|"
+ maxExpense.getCategory()
);
System.out.println();
System.out.println(
"最低支出:"
);
System.out.println(
minExpense.getDate()
+ "|"
+ minExpense.getItem()
+ "|"
+ minExpense.getAmount()
+ " 元|"
+ minExpense.getCategory()
);
}
為什麼一定要先檢查有沒有資料?
注意這一段:
if (expenses.size() == 0) {
System.out.println(
"目前沒有消費紀錄。"
);
return;
}
因為後面會執行:
expenses.get(0);
但如果 ArrayList 是空的:
[]
根本不存在:
index 0
直接執行就會發生錯誤。
所以一定要先確認:
有資料嗎?
↓
沒有 → return
↓
有
↓
expenses.get(0)
這也是之前 Exception Handling 之外,另一種避免程式出錯的方法:
在執行危險操作之前先檢查條件。
修改主選單
昨天有 11 個選項。
今天加入:
完整選單:
===== 個人記帳系統 =====
Switch 加入:
case 11:
analyzeExpenses();
break;
case 12:
running = false;
System.out.println(
"已離開個人記帳系統!"
);
break;
Day 27 完整程式
import java.util.Scanner;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDate;
class Expense {
private LocalDate date;
private String item;
private int amount;
private String category;
Expense(
LocalDate date,
String item,
int amount,
String category
) {
this.date = date;
this.item = item;
this.amount = amount;
this.category = category;
}
public LocalDate getDate() {
return date;
}
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;
}
}
public void setCategory(String category) {
this.category = category;
}
}
public class Main {
static Scanner scanner =
new Scanner(System.in);
static ArrayList expenses =
new ArrayList<>();
static int budget = 0;
public static void main(String[] args) {
loadExpenses();
boolean running = true;
while (running) {
showMenu();
try {
System.out.print(
"請選擇功能:"
);
int choice =
scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
addExpense();
break;
case 2:
showExpenses();
break;
case 3:
calculateTotal();
break;
case 4:
categoryStatistics();
break;
case 5:
deleteExpense();
break;
case 6:
editExpense();
break;
case 7:
setBudget();
break;
case 8:
checkBudget();
break;
case 9:
searchByDate();
break;
case 10:
searchByKeyword();
break;
case 11:
analyzeExpenses();
break;
case 12:
running = false;
System.out.println(
"已離開個人記帳系統!"
);
break;
default:
System.out.println(
"沒有這個選項!"
);
}
} catch (InputMismatchException e) {
System.out.println(
"輸入錯誤,請輸入數字!"
);
scanner.nextLine();
}
}
scanner.close();
}
public static void showMenu() {
System.out.println();
System.out.println(
"===== 個人記帳系統 ====="
);
System.out.println("1. 新增消費");
System.out.println("2. 查看紀錄");
System.out.println("3. 查看總支出");
System.out.println("4. 分類支出統計");
System.out.println("5. 刪除消費紀錄");
System.out.println("6. 修改消費紀錄");
System.out.println("7. 設定預算");
System.out.println("8. 查看預算狀況");
System.out.println("9. 依日期查詢");
System.out.println("10. 關鍵字搜尋");
System.out.println("11. 消費分析");
System.out.println("12. 離開");
}
public static void addExpense() {
System.out.println();
System.out.println(
"===== 新增消費 ====="
);
LocalDate today =
LocalDate.now();
System.out.print(
"請輸入消費項目:"
);
String item =
scanner.nextLine();
System.out.print(
"請輸入消費金額:"
);
int amount =
scanner.nextInt();
String category =
chooseCategory();
Expense expense =
new Expense(
today,
item,
amount,
category
);
expenses.add(expense);
saveExpenses();
System.out.println(
"記帳成功!"
);
System.out.println(
"日期:" + today
);
}
public static String chooseCategory() {
System.out.println();
System.out.println(
"請選擇消費分類:"
);
System.out.println("1. 飲食");
System.out.println("2. 交通");
System.out.println("3. 購物");
System.out.println("4. 娛樂");
System.out.print(
"請輸入選項:"
);
int choice =
scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 1:
return "飲食";
case 2:
return "交通";
case 3:
return "購物";
case 4:
return "娛樂";
default:
return "其他";
}
}
public static void showExpenses() {
System.out.println();
System.out.println(
"===== 消費紀錄 ====="
);
if (expenses.size() == 0) {
System.out.println(
"目前沒有消費紀錄。"
);
return;
}
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
System.out.println(
(i + 1)
+ ". "
+ expense.getDate()
+ "|"
+ expense.getItem()
+ "|"
+ expense.getAmount()
+ " 元|"
+ expense.getCategory()
);
}
}
public static void calculateTotal() {
int total = 0;
for (int i = 0;
i < expenses.size();
i++) {
total +=
expenses.get(i)
.getAmount();
}
System.out.println();
System.out.println(
"===== 支出統計 ====="
);
System.out.println(
"目前共有 "
+ expenses.size()
+ " 筆消費"
);
System.out.println(
"總支出:"
+ total
+ " 元"
);
}
public static void categoryStatistics() {
int foodTotal = 0;
int transportTotal = 0;
int shoppingTotal = 0;
int entertainmentTotal = 0;
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
switch (
expense.getCategory()
) {
case "飲食":
foodTotal +=
expense.getAmount();
break;
case "交通":
transportTotal +=
expense.getAmount();
break;
case "購物":
shoppingTotal +=
expense.getAmount();
break;
case "娛樂":
entertainmentTotal +=
expense.getAmount();
break;
}
}
System.out.println();
System.out.println(
"===== 分類支出統計 ====="
);
System.out.println(
"飲食:"
+ foodTotal
+ " 元"
);
System.out.println(
"交通:"
+ transportTotal
+ " 元"
);
System.out.println(
"購物:"
+ shoppingTotal
+ " 元"
);
System.out.println(
"娛樂:"
+ entertainmentTotal
+ " 元"
);
}
public static void deleteExpense() {
System.out.println();
System.out.println(
"===== 刪除消費紀錄 ====="
);
if (expenses.size() == 0) {
System.out.println(
"目前沒有可以刪除的紀錄。"
);
return;
}
showExpenses();
System.out.print(
"請輸入要刪除的紀錄編號:"
);
int number =
scanner.nextInt();
scanner.nextLine();
if (
number >= 1
&& number <= expenses.size()
) {
Expense deleted =
expenses.remove(
number - 1
);
saveExpenses();
System.out.println(
"已刪除:"
+ deleted.getItem()
);
} else {
System.out.println(
"沒有這筆紀錄!"
);
}
}
public static void editExpense() {
System.out.println();
System.out.println(
"===== 修改消費紀錄 ====="
);
if (expenses.size() == 0) {
System.out.println(
"目前沒有可以修改的紀錄。"
);
return;
}
showExpenses();
System.out.print(
"請輸入要修改的紀錄編號:"
);
int number =
scanner.nextInt();
scanner.nextLine();
if (
number >= 1
&& number <= expenses.size()
) {
Expense expense =
expenses.get(
number - 1
);
System.out.print(
"請輸入新的消費項目:"
);
String item =
scanner.nextLine();
System.out.print(
"請輸入新的消費金額:"
);
int amount =
scanner.nextInt();
String category =
chooseCategory();
expense.setItem(item);
expense.setAmount(amount);
expense.setCategory(category);
saveExpenses();
System.out.println(
"修改成功!"
);
} else {
System.out.println(
"沒有這筆紀錄!"
);
}
}
public static void setBudget() {
System.out.println();
System.out.println(
"===== 設定預算 ====="
);
System.out.print(
"請輸入預算金額:"
);
budget =
scanner.nextInt();
scanner.nextLine();
System.out.println(
"預算設定成功:"
+ budget
+ " 元"
);
}
public static void checkBudget() {
System.out.println();
System.out.println(
"===== 預算狀況 ====="
);
if (budget <= 0) {
System.out.println(
"尚未設定預算。"
);
return;
}
int total = 0;
for (int i = 0;
i < expenses.size();
i++) {
total +=
expenses.get(i)
.getAmount();
}
System.out.println(
"目前預算:"
+ budget
+ " 元"
);
System.out.println(
"目前支出:"
+ total
+ " 元"
);
if (total > budget) {
int over =
total - budget;
System.out.println(
"已超出預算 "
+ over
+ " 元!"
);
} else {
int remaining =
budget - total;
System.out.println(
"剩餘預算:"
+ remaining
+ " 元"
);
}
}
public static void searchByDate() {
System.out.println();
System.out.println(
"===== 依日期查詢 ====="
);
System.out.print(
"請輸入查詢日期(YYYY-MM-DD):"
);
String input =
scanner.nextLine();
try {
LocalDate searchDate =
LocalDate.parse(input);
int total = 0;
int count = 0;
System.out.println();
System.out.println(
"===== "
+ searchDate
+ " 消費紀錄 ====="
);
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
if (
expense.getDate()
.equals(searchDate)
) {
System.out.println(
expense.getItem()
+ "|"
+ expense.getAmount()
+ " 元|"
+ expense.getCategory()
);
total +=
expense.getAmount();
count++;
}
}
if (count == 0) {
System.out.println(
"這一天沒有消費紀錄。"
);
} else {
System.out.println();
System.out.println(
"當日共有 "
+ count
+ " 筆消費"
);
System.out.println(
"當日總支出:"
+ total
+ " 元"
);
}
} catch (Exception e) {
System.out.println(
"日期格式錯誤,請使用 YYYY-MM-DD。"
);
}
}
public static void searchByKeyword() {
System.out.println();
System.out.println(
"===== 關鍵字搜尋 ====="
);
System.out.print(
"請輸入搜尋關鍵字:"
);
String keyword =
scanner.nextLine();
int count = 0;
int total = 0;
System.out.println();
System.out.println(
"===== 搜尋結果 ====="
);
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
if (
expense.getItem()
.contains(keyword)
) {
System.out.println(
expense.getDate()
+ "|"
+ expense.getItem()
+ "|"
+ expense.getAmount()
+ " 元|"
+ expense.getCategory()
);
count++;
total +=
expense.getAmount();
}
}
if (count == 0) {
System.out.println(
"找不到符合的消費紀錄。"
);
} else {
System.out.println();
System.out.println(
"找到 "
+ count
+ " 筆紀錄"
);
System.out.println(
"合計支出:"
+ total
+ " 元"
);
}
}
public static void analyzeExpenses() {
System.out.println();
System.out.println(
"===== 消費分析 ====="
);
if (expenses.size() == 0) {
System.out.println(
"目前沒有消費紀錄。"
);
return;
}
Expense maxExpense =
expenses.get(0);
Expense minExpense =
expenses.get(0);
for (int i = 1;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
if (
expense.getAmount()
> maxExpense.getAmount()
) {
maxExpense = expense;
}
if (
expense.getAmount()
< minExpense.getAmount()
) {
minExpense = expense;
}
}
System.out.println();
System.out.println(
"最高支出:"
);
System.out.println(
maxExpense.getDate()
+ "|"
+ maxExpense.getItem()
+ "|"
+ maxExpense.getAmount()
+ " 元|"
+ maxExpense.getCategory()
);
System.out.println();
System.out.println(
"最低支出:"
);
System.out.println(
minExpense.getDate()
+ "|"
+ minExpense.getItem()
+ "|"
+ minExpense.getAmount()
+ " 元|"
+ minExpense.getCategory()
);
}
public static void saveExpenses() {
try {
FileWriter writer =
new FileWriter(
"expenses.txt"
);
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
writer.write(
expense.getDate()
+ ","
+ expense.getItem()
+ ","
+ expense.getAmount()
+ ","
+ expense.getCategory()
+ "\n"
);
}
writer.close();
} catch (IOException e) {
System.out.println(
"檔案儲存失敗!"
);
}
}
public static void loadExpenses() {
try {
File file =
new File(
"expenses.txt"
);
Scanner fileScanner =
new Scanner(file);
while (
fileScanner.hasNextLine()
) {
String line =
fileScanner.nextLine();
String[] data =
line.split(",");
if (data.length == 4) {
LocalDate date =
LocalDate.parse(
data[0]
);
String item =
data[1];
int amount =
Integer.parseInt(
data[2]
);
String category =
data[3];
Expense expense =
new Expense(
date,
item,
amount,
category
);
expenses.add(
expense
);
}
}
fileScanner.close();
System.out.println(
"記帳資料讀取成功!"
);
} catch (Exception e) {
System.out.println(
"目前沒有已儲存的記帳資料。"
);
}
}
}
實際測試
假設現在有五筆紀錄:
===== 消費紀錄 =====
選擇:
請選擇功能:11
程式執行:
===== 消費分析 =====
最高支出:
2026-10-01|電影|300 元|娛樂
最低支出:
2026-09-30|捷運|30 元|交通
這樣就能很快看出目前紀錄中最大與最小的一筆消費。
今天最重要的觀念:先假設第一筆
今天最值得記住的其實不是新的 Java 語法,而是一種解題方式。
假設資料:
60、30、150、300、80
找最大值:
先假設 60 最大
↓
30 > 60?否
↓
150 > 60?是
↓
最大改成 150
↓
300 > 150?是
↓
最大改成 300
↓
80 > 300?否
↓
答案 = 300
找最小值也是完全相同的概念,只是把:
換成:
<
這個概念以後不只是記帳系統,在處理成績、價格、溫度或其他資料時都會用到。
Day 27 學習心得
今天完成了最高與最低支出的分析功能。和前幾天相比,今天沒有學新的 Class,而是練習如何利用迴圈和條件判斷分析 ArrayList 裡面的資料。
一開始我原本會想直接設定最大值為 0,但最低值就比較難處理。後來學到可以先把第一筆資料當成目前的最大值和最小值,再從第二筆開始逐筆比較,這樣程式會簡單很多。
我也發現記帳系統做到現在,已經不只是新增和保存資料,而是開始可以從累積的資料中找出一些資訊,例如總支出、分類支出、指定日期支出,以及今天完成的最高和最低支出。