昨天 Day 22 完成了檔案讀取,現在重新開啟程式時,可以從 expenses.txt 把以前的消費紀錄讀回來。
目前已經做到:
啟動程式
↓
讀取 expenses.txt
↓
恢復以前的記帳紀錄 ✓
但是還有一個使用上的問題。
每次新增、修改或刪除資料後,都必須自己選擇:
如果忘記按儲存就直接離開程式,剛剛修改的資料還是會消失。
所以 Day 23 要讓系統變得更方便:每次資料發生變化,就自動儲存。
今天要完成的功能
原本:
新增消費
↓
資料加入 ArrayList
↓
手動選擇「儲存」
↓
寫入 expenses.txt
今天改成:
新增消費
↓
資料加入 ArrayList
↓
saveExpenses()
↓
自動寫入 expenses.txt
修改和刪除也是一樣。
這樣使用者就不需要一直記得按「儲存」。
第一步:新增消費後自動儲存
原本 addExpense() 最後是:
expenses.add(expense);
System.out.println("記帳成功!");
今天改成:
expenses.add(expense);
saveExpenses();
System.out.println("記帳成功!");
這樣每新增一筆消費:
早餐|60 元|飲食
程式就會立刻把目前所有資料重新寫入:
expenses.txt
第二步:刪除後自動儲存
原本:
Expense deleted =
expenses.remove(number - 1);
System.out.println(
"已刪除:" + deleted.getItem()
);
今天加入:
saveExpenses();
變成:
Expense deleted =
expenses.remove(number - 1);
saveExpenses();
System.out.println(
"已刪除:" + deleted.getItem()
);
假設原本檔案:
早餐,60,飲食
捷運,30,交通
電影,300,娛樂
刪除捷運後,就會自動更新成:
早餐,60,飲食
電影,300,娛樂
第三步:修改後也自動儲存
修改資料的地方原本是:
expense.setItem(item);
expense.setAmount(amount);
expense.setCategory(category);
System.out.println("修改成功!");
加入:
saveExpenses();
變成:
expense.setItem(item);
expense.setAmount(amount);
expense.setCategory(category);
saveExpenses();
System.out.println("修改成功!");
例如:
午餐|120 元|飲食
修改成:
午餐|150 元|飲食
expenses.txt 也會立刻跟著更新。
saveExpenses() 不用重新寫
其實今天不需要重新設計儲存功能。
因為 Day 21 已經寫好了:
public static void saveExpenses()
今天只是把它放到適合的位置。
這也讓我更理解 Method 的好處。
如果當初沒有把儲存功能整理成 Method,今天可能要在新增、修改、刪除三個地方都重新寫一次 FileWriter。
但是現在只需要:
saveExpenses();
就完成了。
把手動儲存從選單移除
既然現在會自動儲存,就不需要:
所以選單可以從:
改成:
===== 個人記帳系統 =====
操作起來也更簡單。
今天順便改善 saveExpenses()
昨天的:
saveExpenses();
會顯示:
記帳資料已儲存!
但現在新增、修改、刪除都會自動呼叫它。
如果每次都顯示:
記帳資料已儲存!
畫面會有點多。
所以今天把成功訊息拿掉,只在發生錯誤時提醒:
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.getItem() + ","
+ expense.getAmount() + ","
+ expense.getCategory() + "\n"
);
}
writer.close();
} catch (IOException e) {
System.out.println("檔案儲存失敗!");
}
}
這樣自動儲存會在背景完成。
Day 23 完整程式
import java.util.Scanner;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
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;
}
}
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:
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. 離開");
}
public static void addExpense() {
System.out.println();
System.out.println(
"===== 新增消費 ====="
);
System.out.print(
"請輸入消費項目:"
);
String item =
scanner.nextLine();
System.out.print(
"請輸入消費金額:"
);
int amount =
scanner.nextInt();
String category =
chooseCategory();
Expense expense =
new Expense(
item,
amount,
category
);
expenses.add(expense);
saveExpenses();
System.out.println(
"記帳成功!資料已自動儲存。"
);
}
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.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()
);
System.out.println(
"資料已自動儲存。"
);
} 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 saveExpenses() {
try {
FileWriter writer =
new FileWriter(
"expenses.txt"
);
for (int i = 0;
i < expenses.size();
i++) {
Expense expense =
expenses.get(i);
writer.write(
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(",");
String item =
data[0];
int amount =
Integer.parseInt(
data[1]
);
String category =
data[2];
Expense expense =
new Expense(
item,
amount,
category
);
expenses.add(expense);
}
fileScanner.close();
System.out.println(
"記帳資料讀取成功!"
);
} catch (Exception e) {
System.out.println(
"目前沒有已儲存的記帳資料。"
);
}
}
}
實際測試:新增資料
假設現在新增:
===== 新增消費 =====
請輸入消費項目:早餐
請輸入消費金額:60
請選擇消費分類:
結果:
記帳成功!資料已自動儲存。
這時候完全不用再按「儲存」。
expenses.txt 已經出現:
早餐,60,飲食
再新增一筆
請輸入消費項目:捷運
請輸入消費金額:30
請輸入選項:2
記帳成功!資料已自動儲存。
檔案自動更新:
早餐,60,飲食
捷運,30,交通
測試刪除
目前:
===== 消費紀錄 =====
選擇:
輸入:
請輸入要刪除的紀錄編號:1
結果:
已刪除:早餐
資料已自動儲存。
現在 expenses.txt 也會自動變成:
捷運,30,交通
所以 ArrayList 和檔案裡的資料可以保持一致。
現在的資料流程更完整了
Day 21:
ArrayList
↓
手動儲存
↓
expenses.txt
Day 22:
expenses.txt
↓
啟動時讀取
↓
ArrayList
Day 23:
啟動程式
↓
loadExpenses()
↓
ArrayList
↓
┌─────────┼─────────┐
↓ ↓ ↓
新增 修改 刪除
↓ ↓ ↓
└─────────┼─────────┘
↓
saveExpenses()
↓
expenses.txt
到這裡,資料保存的流程就完整很多了。
為什麼每次都把整個檔案重新寫一次?
目前使用:
new FileWriter("expenses.txt");
每次 saveExpenses() 都會根據現在的 ArrayList,把整份 expenses.txt 重新寫一次。
例如原本:
早餐,60,飲食
午餐,150,飲食
捷運,30,交通
刪除午餐後,ArrayList 剩下:
早餐
捷運
重新執行 saveExpenses():
早餐,60,飲食
捷運,30,交通
舊檔案被新的內容取代。
對目前這個小型記帳系統來說,這種做法比較簡單,也比較容易理解。
Day 23 學習心得
今天沒有學很多新的 Java 語法,而是改善之前已經完成的檔案儲存功能。
原本每次新增、修改或刪除資料後,都還需要自己選擇「儲存記帳資料」,如果忘記就可能造成資料遺失。今天把 saveExpenses() 放進新增、修改和刪除功能後,資料只要發生變化就會自動寫入檔案。
這次也讓我更理解 Method 的實際用途。因為之前已經把檔案儲存整理成 saveExpenses(),今天只需要在不同功能裡呼叫它,不需要重複寫 FileWriter 的程式。
做到 Day 23,記帳系統已經可以自動載入和自動保存資料,使用方式也比前幾天更完整。