iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
自我挑戰組

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

加入日期:讓每一筆消費知道「哪一天花的」

  • 分享至 

  • xImage
  •  

昨天 Day 23 完成了自動儲存功能,現在新增、修改或刪除消費後,系統都會自動更新 expenses.txt。

目前的消費紀錄看起來像:

  1. 早餐|60 元|飲食
  2. 捷運|30 元|交通
  3. 電影|300 元|娛樂

但我發現真正記帳時還缺少一個很重要的資訊:

這筆錢是哪一天花的?

所以 Day 24 要幫每一筆 Expense 加入「日期」,讓紀錄變成:

  1. 2026-10-02|早餐|60 元|飲食
  2. 2026-10-02|捷運|30 元|交通
  3. 2026-10-03|電影|300 元|娛樂

今天要學 LocalDate

Java 可以使用:

LocalDate

處理「日期」。

首先加入:

import java.time.LocalDate;

接著:

LocalDate today = LocalDate.now();

就可以取得電腦目前的日期。

例如:

2026-10-02

簡單來說:

LocalDate.now()
↓
取得今天日期
↓
2026-10-02

先做一個簡單測試

import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
System.out.println(
"今天日期:" + today
);
}
}

執行結果:

今天日期:2026-10-02

這樣就不用每次記帳都自己輸入日期。

第一步:Expense 加入 date

目前的 Expense 有:

private String item;
private int amount;
private String category;

今天加入:

private LocalDate date;

變成:

class Expense {
private LocalDate date;
private String item;
private int amount;
private String category;
}

這樣每一個 Expense Object 都可以記住自己的日期。

第二步:修改 Constructor

原本:

Expense(
String item,
int amount,
String category
)

今天需要多一個日期:

Expense(
LocalDate date,
String item,
int amount,
String category
)

完整 Constructor:

Expense(
LocalDate date,
String item,
int amount,
String category
) {
this.date = date;
this.item = item;
this.amount = amount;
this.category = category;
}

所以現在建立一筆消費時:

Expense expense =
new Expense(
LocalDate.now(),
"早餐",
60,
"飲食"
);

這筆資料就包含:

日期:2026-10-02
項目:早餐
金額:60
分類:飲食

第三步:加入 getDate()

因為 date 是 private,所以和之前一樣,需要 Getter:

public LocalDate getDate() {
return date;
}

這樣外面的程式就可以:

expense.getDate()

取得日期。

第四步:新增消費時自動取得今天日期

原本 addExpense() 建立 Expense:

Expense expense =
new Expense(
item,
amount,
category
);

今天先取得日期:

LocalDate today =
LocalDate.now();

再建立:

Expense expense =
new Expense(
today,
item,
amount,
category
);

這樣使用者還是只需要輸入:

消費項目
消費金額
消費分類

日期由 Java 自動加入。

第五步:顯示日期

原本:

System.out.println(
(i + 1) + ". "
+ expense.getItem()
+ "|"
+ expense.getAmount()
+ " 元|"
+ expense.getCategory()
);

今天改成:

System.out.println(
(i + 1) + ". "
+ expense.getDate()
+ "|"
+ expense.getItem()
+ "|"
+ expense.getAmount()
+ " 元|"
+ expense.getCategory()
);

結果:

  1. 2026-10-02|早餐|60 元|飲食
  2. 2026-10-02|捷運|30 元|交通

現在看起來就更像真正的記帳紀錄了。

第六步:儲存日期

昨天的 expenses.txt 格式:

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

今天改成:

日期,項目,金額,分類

例如:

2026-10-02,早餐,60,飲食
2026-10-02,捷運,30,交通

所以 saveExpenses() 改成:

writer.write(
expense.getDate() + ","
+ expense.getItem() + ","
+ expense.getAmount() + ","
+ expense.getCategory() + "\n"
);

現在每一行會有四個資料。

第七步:讀取日期

原本 Day 22:

String[] data =
line.split(",");

以前:

data[0] → 早餐
data[1] → 60
data[2] → 飲食

現在格式改變:

2026-10-02,早餐,60,飲食

所以變成:

data[0] → 2026-10-02
data[1] → 早餐
data[2] → 60
data[3] → 飲食

日期目前也是文字,所以要把它轉成 LocalDate:

LocalDate date =
LocalDate.parse(data[0]);

這和昨天:

Integer.parseInt(data[2])

概念很像。

一個是:

"60"
↓
Integer.parseInt()
↓
int 60

另一個是:

"2026-10-02"
↓
LocalDate.parse()
↓
LocalDate

Day 24 完整程式

今天把日期整合進目前的記帳系統:

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:
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(
"===== 新增消費 ====="
);
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 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(
"目前沒有已儲存的記帳資料。"
);
}
}
}

為什麼多了 data.length == 4?

今天我另外加入:

if (data.length == 4)

因為現在一筆完整資料應該包含四個部分:

日期,項目,金額,分類

也就是:

data[0]
data[1]
data[2]
data[3]

如果某一行資料格式不完整,就先不要建立 Expense,避免程式直接出錯。

這算是一個簡單的資料檢查。

注意:昨天的舊檔案格式不同

這裡有一個今天實際修改系統時很重要的地方。

Day 23 的檔案可能還是:

早餐,60,飲食
午餐,150,飲食

但是 Day 24 已經改成:

2026-10-02,早餐,60,飲食
2026-10-02,午餐,150,飲食

所以舊資料只有 3 個欄位,新資料有 4 個欄位。

今天的程式:

if (data.length == 4)

只會讀取新的格式。

如果是自己跟著這個系列實作,可以先刪除之前測試用的 expenses.txt,再重新執行 Day 24 的版本建立新的測試資料。

實際測試

新增第一筆:

===== 新增消費 =====
請輸入消費項目:早餐
請輸入消費金額:60
請選擇消費分類:

  1. 飲食
  2. 交通
  3. 購物
  4. 娛樂
    請輸入選項:1
    記帳成功!
    日期:2026-10-02

再新增:

請輸入消費項目:捷運
請輸入消費金額:30
請輸入選項:2
記帳成功!
日期:2026-10-02

查看紀錄:

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

  1. 2026-10-02|早餐|60 元|飲食
  2. 2026-10-02|捷運|30 元|交通

打開 expenses.txt:

2026-10-02,早餐,60,飲食
2026-10-02,捷運,30,交通

關閉程式再重新開啟,執行:

loadExpenses();

資料依然可以正常讀取。

今天其實把很多以前學過的東西串起來了

今天的新東西主要是:

LocalDate
LocalDate.now()
LocalDate.parse()

但是完成整個功能時,又用到了之前很多內容:

Class / Object
↓
Expense
Constructor
↓
建立含日期的 Expense
Getter
↓
getDate()
ArrayList
↓
保存 Expense
for
↓
顯示與儲存所有紀錄
while
↓
逐行讀取檔案
Array
↓
String[] data
split()
↓
拆開檔案內容
FileWriter
↓
保存資料
Exception Handling
↓
處理檔案錯誤

開始有一種前面每天學的東西都慢慢組合起來的感覺。

Day 24 學習心得

今天替每一筆消費加入日期,第一次使用 Java 的 LocalDate。透過 LocalDate.now(),新增消費時可以自動取得當天日期,不需要讓使用者另外輸入。

加入日期之後,也需要一起修改 Expense Class、Constructor、Getter、檔案儲存格式和檔案讀取方式。原本以為只是多一個日期而已,實際做才發現資料結構改變後,很多相關的程式也要一起調整。

目前每筆消費已經包含日期、項目、金額和分類,記帳資料比之前完整很多,也為後面按照日期查詢或統計消費做好準備。


上一篇
自動儲存:新增、修改、刪除後直接保存資料
下一篇
依日期查詢:找出指定日期的所有消費
系列文
Java 程式設計與實作:30 天打造個人記帳系統 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言