概要

単位変換のコードで怖いのは、変換式の誤りよりも「どの単位で値を持っているか」の認識ズレです。DB にはグラムで入っているのに画面はキログラム前提だった、海外仕様書の温度が華氏だったのに摂氏として計算していた――このたぐいの不具合はコンパイルも通り、テストも一見通ってしまうため、発見が遅れがちです。実装面では、N 種類の単位を相互変換しようとして N×N 通りの変換式を書き始めると保守が破綻します。この記事では、enum に基準単位への係数を持たせて「基準単位を経由して変換する」設計パターンを軸に、BigDecimal での係数管理、温度のようにオフセットが絡む単位系の扱い、単位を追加するときの拡張手順を整理します。

使いどころ

海外倉庫とのデータ連携で、lb 単位で届く重量を kg に揃えて在庫システムへ取り込む

図面管理システムで inch 表記の寸法を mm に変換し、公差計算の基準を統一する

設備モニタリングで華氏で送られてくるセンサー値を摂氏に変換して閾値判定にかける

コード例

基準単位経由の単位変換
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

public class UnitConverter {

    enum LengthUnit {
        METER(BigDecimal.ONE),
        CENTIMETER(new BigDecimal("0.01")),
        MILLIMETER(new BigDecimal("0.001")),
        KILOMETER(new BigDecimal("1000")),
        INCH(new BigDecimal("0.0254")),
        FOOT(new BigDecimal("0.3048"));

        final BigDecimal toMeter;

        LengthUnit(BigDecimal toMeter) {
            this.toMeter = toMeter;
        }
    }

    private static final MathContext MC =
        new MathContext(10, RoundingMode.HALF_UP);

    public static BigDecimal convert(
            BigDecimal value,
            LengthUnit from,
            LengthUnit to) {
        var meters = value.multiply(from.toMeter);
        return meters.divide(to.toMeter, MC);
    }

    public static void main(String[] args) {
        var feet = new BigDecimal("6");
        var cm = convert(feet,
            LengthUnit.FOOT, LengthUnit.CENTIMETER);
        System.out.println(feet + " ft = "
            + cm.setScale(2, RoundingMode.HALF_UP) + " cm");
        // → 6 ft = 182.88 cm

        // 逆変換して元の値に戻ることを確認(往復テストの基本形)
        var back = convert(cm,
            LengthUnit.CENTIMETER, LengthUnit.FOOT);
        System.out.println("往復: "
            + back.setScale(2, RoundingMode.HALF_UP) + " ft");
        // → 6.00 ft

        // 桁の離れた変換も基準単位(メートル)経由なら崩れない
        var mm = new BigDecimal("1500000");
        System.out.println(mm + " mm = "
            + convert(mm, LengthUnit.MILLIMETER, LengthUnit.KILOMETER)
                .setScale(1, RoundingMode.HALF_UP) + " km");
        // → 1500000 mm = 1.5 km
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

record + switch 式で変換ロジックを簡潔に書ける。var による型推論で記述量も減る。

Java 17
// Java 17: record + switch 式で変換ロジック
record Conversion(BigDecimal value, String unit) {}
var result = switch (to) {
    case METER      -> meters;
    case CENTIMETER -> meters.divide(
        CENTIMETER.toMeter, MC);
    case INCH       -> meters.divide(
        INCH.toMeter, MC);
};
var output = new Conversion(result, to.name());

Library Comparison

JSR 385 (Units of Measurement)物理量の型安全な演算が必要な場合。外部依存が増える。基本的な変換なら自前で十分。
自前 enum パターン(標準 API)変換対象の単位系が限定的で、係数を enum に持たせるだけで済む場合。依存ゼロで実装でき、拡張も enum 定数の追加だけで完結する。温度のようにオフセット変換が必要な単位系では enum の係数方式だけでは対応できず、個別ロジックの追加が必要になる。

注意点

温度変換は単純な係数乗算ではないため、別途ロジックが必要。

BigDecimal で係数を管理しないと浮動小数点誤差が入る。

単位の追加時は enum にフィールドを追加するだけで拡張できる設計にする。

現場では単位の取り違えが原因の不具合が意外と多い。DB や外部 API とのデータ授受では単位を明示するコメントや定数名をつける習慣をつけること。グラムとキログラムの混在は特に見逃しやすい。

FAQ

基準単位方式にすると何が楽になりますか?

単位を増やすときの修正が「基準単位への係数を1つ追加する」だけで済みます。相互変換式を個別に書く方式では、単位数の2乗のペースで式が増えていきます。

温度変換も同じ enum 方式で書けますか?

摂氏・華氏はオフセット(32度)の加減算が入るため、係数の乗除だけでは表現できません。温度だけは専用の変換メソッドに分離するのが素直な設計です。

変換係数を double で持つと何が起きますか?

0.0254 のような係数は2進数で誤差を含むため、乗除を繰り返すと下位桁がずれます。検査成績書や請求に関わる値なら BigDecimal で管理してください。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます