昨天的 Day 11,我們學會了使用 @Valid、@NotNull 和 @Positive,讓 Spring Boot 可以自動檢查使用者輸入的資料。
目前我們的智慧健康助理已經可以接收身高與體重、驗證資料,並計算 BMI。
例如:
{
"height": 162,
"weight": 50,
"bmi": 19.05
}
不過今天我想到一個問題:
如果使用者只看到 BMI = 19.05,真的知道這個數字代表什麼嗎?
可能有些人知道,但也可能有人完全沒有概念。
所以今天決定讓 BMI 計算器再進化一點!
除了計算 BMI,還要讓系統根據計算結果,自動顯示對應的 BMI 分類。
今天的目標是:
利用 Java 的 if-else 條件判斷,讓智慧健康助理可以自動分類 BMI。
前幾天我們已經學會 BMI 的計算公式:
BMI = 體重(kg) ÷ 身高²(m²)
但 BMI 不只是一個數字,也經常被用來作為成人體重狀態的初步篩檢指標。
今天先參考台灣成人常用的 BMI 分類標準:
| BMI 範圍 | 分類 |
|---|---|
| BMI < 18.5 | 體重過輕 |
| 18.5 ≤ BMI < 24 | 正常範圍 |
| 24 ≤ BMI < 27 | 體重過重 |
| BMI ≥ 27 | 肥胖 |
例如:
BMI = 19.05
對照上面的分類,就屬於:
正常範圍
不過要注意,這些標準主要適用於一般成年人,兒童、青少年及孕婦等族群需要使用不同的評估方式。
而且 BMI 無法直接測量體脂肪,也不能單獨判斷一個人是否健康。
因此今天的功能只會提供 BMI 分類資訊,不會進行疾病診斷。
在 Day 10,我們已經將 BMI 計算邏輯從 Controller 移到 Service。
目前的 BmiService.java 大致如下:
@Service
public class BmiService {
public double calculateBMI(double height, double weight) {
if (!Double.isFinite(height) ||
!Double.isFinite(weight) ||
height <= 0 || weight <= 0) {
throw new IllegalArgumentException(
"Height and weight must be positive numbers."
);
}
double heightInMeters = height / 100;
double bmi = weight / (heightInMeters * heightInMeters);
if (!Double.isFinite(bmi)) {
throw new IllegalArgumentException(
"Unable to calculate BMI."
);
}
return Math.round(bmi * 100.0) / 100.0;
}
}
這段程式已經可以計算 BMI。
今天不需要重新設計整個 API,只要在 Service 裡面新增一個分類方法就可以了。
這也是之前把 Controller 和 Service 分開的好處!
今天要使用的是 Java 很基本,但也非常重要的條件判斷:
if
else if
else
例如:
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else {
System.out.println("C");
}
程式會依照條件,決定要執行哪一段程式碼。
我們也可以用同樣的方式判斷 BMI。
例如:
if (bmi < 18.5) {
return "體重過輕";
}
代表:
如果 BMI 小於 18.5,就回傳「體重過輕」。
接著再加入其他條件,就可以完成 BMI 分類功能。
接下來在 BmiService.java 裡面新增一個 Method:
public String getBmiCategory(double bmi) {
if (bmi < 18.5) {
return "體重過輕";
} else if (bmi < 24) {
return "正常範圍";
} else if (bmi < 27) {
return "體重過重";
} else {
return "肥胖";
}
}
這段程式的邏輯其實不難。
Java 會從上往下檢查條件。
例如:
BMI = 19.05
第一個條件:
bmi < 18.5
不成立。
接著檢查:
bmi < 24
成立!
所以就會回傳:
正常範圍
因為前面已經排除了 BMI 小於 18.5 的情況,所以第二個條件不需要再重複寫:
bmi >= 18.5
這也是 if-else 很方便的地方。
現在把 BMI 計算和分類功能放在同一個 Service 裡面。
完整程式如下:
package com.example.healthassistant.service;
import org.springframework.stereotype.Service;
@Service
public class BmiService {
public double calculateBMI(double height, double weight) {
if (!Double.isFinite(height) ||
!Double.isFinite(weight) ||
height <= 0 || weight <= 0) {
throw new IllegalArgumentException(
"Height and weight must be positive numbers."
);
}
double heightInMeters = height / 100;
double bmi = weight / (heightInMeters * heightInMeters);
if (!Double.isFinite(bmi)) {
throw new IllegalArgumentException(
"Unable to calculate BMI."
);
}
return Math.round(bmi * 100.0) / 100.0;
}
public String getBmiCategory(double bmi) {
if (bmi < 18.5) {
return "體重過輕";
} else if (bmi < 24) {
return "正常範圍";
} else if (bmi < 27) {
return "體重過重";
} else {
return "肥胖";
}
}
}
現在 BmiService 已經有兩個 Method:
calculateBMI()
負責計算 BMI。
以及:
getBmiCategory()
負責判斷 BMI 分類。
這樣不同的工作就可以分開處理。
接下來要讓 Controller 同時取得 BMI 數值和分類結果。
原本只有:
double bmi = bmiService.calculateBMI(height, weight);
現在再加入:
String category = bmiService.getBmiCategory(bmi);
接著把分類結果放進 JSON。
完整程式如下:
package com.example.healthassistant.controller;
import com.example.healthassistant.dto.HealthRequest;
import com.example.healthassistant.service.BmiService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HealthController {
private final BmiService bmiService;
public HealthController(BmiService bmiService) {
this.bmiService = bmiService;
}
@PostMapping("/health/bmi")
public ResponseEntity<Map<String, Object>> calculateBMI(
@Valid @RequestBody HealthRequest request) {
double height = request.getHeight();
double weight = request.getWeight();
double bmi = bmiService.calculateBMI(height, weight);
String category = bmiService.getBmiCategory(bmi);
Map<String, Object> result = new HashMap<>();
result.put("height", height);
result.put("weight", weight);
result.put("bmi", bmi);
result.put("category", category);
return ResponseEntity.ok(result);
}
}
這次只增加了兩個重要的地方。
第一個:
String category = bmiService.getBmiCategory(bmi);
呼叫 Service 取得 BMI 分類。
第二個:
result.put("category", category);
將分類結果加入 JSON。
這樣就完成了!
接下來重新啟動 Spring Boot。
在 Postman 設定:
POST http://localhost:8080/health/bmi
Body 選擇:
raw → JSON
輸入:
{
"height": 162,
"weight": 50
}
預期回傳:
{
"height": 162.0,
"weight": 50.0,
"bmi": 19.05,
"category": "正常範圍"
}
和昨天相比,現在多了一個:
"category": "正常範圍"
這代表系統不只能計算 BMI,還可以自動判斷分類。
終於又多了一個小功能 XD
為了確認 if-else 有沒有寫錯,我們可以測試不同的身高和體重。
例如固定身高:
160 cm
再改變體重。
| 身高 | 體重 | BMI | 分類 |
|---|---|---|---|
| 160 cm | 45 kg | 17.58 | 體重過輕 |
| 160 cm | 55 kg | 21.48 | 正常範圍 |
| 160 cm | 65 kg | 25.39 | 體重過重 |
| 160 cm | 75 kg | 29.30 | 肥胖 |
例如輸入:
{
"height": 160,
"weight": 65
}
預期回傳:
{
"height": 160.0,
"weight": 65.0,
"bmi": 25.39,
"category": "體重過重"
}
這樣就可以確認不同 BMI 數值會進入不同的判斷條件。
今天我們先使用:
return Math.round(bmi * 100.0) / 100.0;
把 BMI 四捨五入到小數點後兩位。
不過這裡其實有一個小細節。
假設原始 BMI 是:
23.996
四捨五入後就會變成:
24.00
如果我們用四捨五入後的數值判斷分類,就會得到:
體重過重
但原始 BMI 其實還沒有達到 24。
因此更嚴謹的做法是:
先用未四捨五入的 BMI 判斷分類,最後才將顯示用的 BMI 四捨五入。
這樣可以避免在分類邊界附近出現判斷誤差。
所以我們可以稍微調整 Service。
將原本的:
return Math.round(bmi * 100.0) / 100.0;
改成:
return bmi;
讓 calculateBMI() 回傳原始計算結果。
接著在 Controller 裡面:
double bmi = bmiService.calculateBMI(height, weight);
String category = bmiService.getBmiCategory(bmi);
double roundedBmi = Math.round(bmi * 100.0) / 100.0;
最後回傳:
result.put("bmi", roundedBmi);
result.put("category", category);
這樣就能同時兼顧:
雖然只是很小的調整,但也讓我發現,寫程式時真的需要注意一些容易忽略的細節。
做到這裡,還有一件事情我覺得很重要。
我們今天的程式可以回傳:
{
"bmi": 19.05,
"category": "正常範圍"
}
但這不代表系統可以直接判斷使用者完全健康。
因為 BMI 只考慮:
身高
體重
沒有直接考慮:
例如運動員可能因為肌肉量較高,而有較高的 BMI。
因此 BMI 比較適合作為初步參考,而不是完整的健康評估。
未來如果要加入 AI 功能,也應該讓 AI 清楚說明這些限制,而不是直接根據 BMI 做出疾病診斷。
今天的 BMI 分類,其實也不需要使用 AI。
因為分類標準已經有明確的數值範圍。
例如:
BMI < 18.5
或是:
18.5 ≤ BMI < 24
這些都可以直接使用 Java 的條件判斷完成。
如果交給 AI 來判斷,反而可能增加不必要的不確定性。
所以目前的分工仍然是:
Java
├── 接收健康資料
├── 驗證輸入
├── 計算 BMI
└── 判斷 BMI 分類
AI
└── 未來協助整理健康資訊與文字摘要
我覺得這也是這個專案很重要的觀念:
不是所有看起來很智慧的功能,都一定要使用 AI。
有些事情使用傳統程式邏輯,反而更直接也更容易驗證。
經過今天的更新,整個 BMI API 的流程變成:
使用者輸入身高與體重
↓
POST /health/bmi
↓
HealthRequest DTO
↓
@Valid 驗證資料
↓
HealthController
↓
BmiService
↓
計算原始 BMI
↓
判斷 BMI 分類
↓
四捨五入顯示數值
↓
HealthController
↓
回傳 JSON
原本只有:
{
"bmi": 19.05
}
現在則可以回傳:
{
"bmi": 19.05,
"category": "正常範圍"
}
雖然只是增加一個欄位,但對使用者來說,資訊已經更容易理解了。
今天終於讓 BMI 計算器加入了分類功能!
除了計算 BMI,現在還能根據數值回傳:
今天也複習了 Java 的:
if
else if
else
並學會將分類邏輯放進 Service,而不是全部寫在 Controller 裡面。
另外也發現,計算結果的顯示方式和實際判斷邏輯,最好分開處理,避免四捨五入影響分類結果。
目前智慧健康助理已經可以:
接收資料
↓
驗證資料
↓
計算 BMI
↓
判斷 BMI 分類
↓
回傳 JSON
距離一開始只會回傳 Hello 的程式,真的進步不少了 XD