iT邦幫忙

2026 iThome 鐵人賽

0
AI Engineering

Java × AI:30天打造智慧健康助理系列 第 12 篇

使用 Java 加入 BMI 分類功能

  • 分享至 

  • xImage
  •  

Day 12|使用 Java 加入 BMI 分類功能

昨天的 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 的計算公式:

BMI = 體重(kg) ÷ 身高²(m²)

但 BMI 不只是一個數字,也經常被用來作為成人體重狀態的初步篩檢指標。

今天先參考台灣成人常用的 BMI 分類標準:

BMI 範圍 分類
BMI < 18.5 體重過輕
18.5 ≤ BMI < 24 正常範圍
24 ≤ BMI < 27 體重過重
BMI ≥ 27 肥胖

例如:

BMI = 19.05

對照上面的分類,就屬於:

正常範圍

不過要注意,這些標準主要適用於一般成年人,兒童、青少年及孕婦等族群需要使用不同的評估方式。

而且 BMI 無法直接測量體脂肪,也不能單獨判斷一個人是否健康。

因此今天的功能只會提供 BMI 分類資訊,不會進行疾病診斷。


二、先回顧目前的 BmiService

在 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

今天要使用的是 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 分類功能。


四、建立 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 很方便的地方。


五、修改完整的 BmiService

現在把 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 分類。

這樣不同的工作就可以分開處理。


六、修改 HealthController

接下來要讓 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。

這樣就完成了!


七、使用 Postman 測試

接下來重新啟動 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


八、測試不同的 BMI 分類

為了確認 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 分類不等於健康診斷

做到這裡,還有一件事情我覺得很重要。

我們今天的程式可以回傳:

{
  "bmi": 19.05,
  "category": "正常範圍"
}

但這不代表系統可以直接判斷使用者完全健康。

因為 BMI 只考慮:

身高
體重

沒有直接考慮:

  • 肌肉量
  • 體脂肪比例
  • 年齡
  • 身體組成
  • 其他健康狀況

例如運動員可能因為肌肉量較高,而有較高的 BMI。

因此 BMI 比較適合作為初步參考,而不是完整的健康評估。

未來如果要加入 AI 功能,也應該讓 AI 清楚說明這些限制,而不是直接根據 BMI 做出疾病診斷。


十一、這個功能需要 AI 嗎?

今天的 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": "正常範圍"
}

雖然只是增加一個欄位,但對使用者來說,資訊已經更容易理解了。


Day 12 小結

今天終於讓 BMI 計算器加入了分類功能!

除了計算 BMI,現在還能根據數值回傳:

  • 體重過輕
  • 正常範圍
  • 體重過重
  • 肥胖

今天也複習了 Java 的:

if
else if
else

並學會將分類邏輯放進 Service,而不是全部寫在 Controller 裡面。

另外也發現,計算結果的顯示方式和實際判斷邏輯,最好分開處理,避免四捨五入影響分類結果。

目前智慧健康助理已經可以:

接收資料
    ↓
驗證資料
    ↓
計算 BMI
    ↓
判斷 BMI 分類
    ↓
回傳 JSON

距離一開始只會回傳 Hello 的程式,真的進步不少了 XD


明日預告

Day 13|不只 BMI!加入每日飲


上一篇
使用 DTO 與 @Valid 驗證使用者輸入
下一篇
用 Java 打造每日飲水量紀錄功能
系列文
Java × AI:30天打造智慧健康助理 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言