iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
自我挑戰組

Java 程式設計與實作:30 天打造個人記帳系統系列 第 23 篇

自動儲存:新增、修改、刪除後直接保存資料

  • 分享至 

  • xImage
  •  

昨天 Day 22 完成了檔案讀取,現在重新開啟程式時,可以從 expenses.txt 把以前的消費紀錄讀回來。

目前已經做到:

啟動程式
↓
讀取 expenses.txt
↓
恢復以前的記帳紀錄 ✓

但是還有一個使用上的問題。

每次新增、修改或刪除資料後,都必須自己選擇:

  1. 儲存記帳資料

如果忘記按儲存就直接離開程式,剛剛修改的資料還是會消失。

所以 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();

就完成了。

把手動儲存從選單移除

既然現在會自動儲存,就不需要:

  1. 儲存記帳資料

所以選單可以從:

  1. 新增消費
  2. 查看紀錄
  3. 查看總支出
  4. 分類支出統計
  5. 刪除消費紀錄
  6. 修改消費紀錄
  7. 設定預算
  8. 查看預算狀況
  9. 儲存記帳資料
  10. 離開

改成:

===== 個人記帳系統 =====

  1. 新增消費
  2. 查看紀錄
  3. 查看總支出
  4. 分類支出統計
  5. 刪除消費紀錄
  6. 修改消費紀錄
  7. 設定預算
  8. 查看預算狀況
  9. 離開

操作起來也更簡單。

今天順便改善 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
請選擇消費分類:

  1. 飲食
  2. 交通
  3. 購物
  4. 娛樂
    請輸入選項:1

結果:

記帳成功!資料已自動儲存。

這時候完全不用再按「儲存」。

expenses.txt 已經出現:

早餐,60,飲食

再新增一筆

請輸入消費項目:捷運
請輸入消費金額:30
請輸入選項:2
記帳成功!資料已自動儲存。

檔案自動更新:

早餐,60,飲食
捷運,30,交通

測試刪除

目前:

===== 消費紀錄 =====

  1. 早餐|60 元|飲食
  2. 捷運|30 元|交通

選擇:

  1. 刪除消費紀錄

輸入:

請輸入要刪除的紀錄編號: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,記帳系統已經可以自動載入和自動保存資料,使用方式也比前幾天更完整。


上一篇
File 檔案讀取:重新開啟程式也能看到以前的記帳紀錄
下一篇
加入日期:讓每一筆消費知道「哪一天花的」
系列文
Java 程式設計與實作:30 天打造個人記帳系統 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言