Java Tips | 文字列処理:MessageFormat簡易化

Java Java
スポンサーリンク

MessageFormat簡易化は「よく使う型を“いい感じに包む”」テクニック

MessageFormat は、国際化(i18n)対応も視野に入れた強力なフォーマットクラスですが、
初心者からすると「書き方が独特」「毎回ちょっと面倒」に感じやすい道具でもあります。

"こんにちは {0} さん" のようなテンプレートに値を埋め込めるのは便利だけれど、
毎回 MessageFormat.format("...", args...) と書くのは少し重い。
数値や日付の書式指定もできるけれど、そこまで使わない場面も多い。

そこで現場では、「自分たちがよく使うパターンだけを、もっと簡単に呼べるようにラップする」
つまり MessageFormat簡易化ユーティリティ を用意することがよくあります。


まずは素の MessageFormat をきちんと理解する

{0} で位置指定、MessageFormat.format で埋め込む

基本形はとてもシンプルです。

import java.text.MessageFormat;

public class MessageFormatBasic {

    public static void main(String[] args) {
        String template = "こんにちは {0} さん。今日は {1} です。";
        String result = MessageFormat.format(template, "山田", "2026-02-04");
        System.out.println(result);
        // → こんにちは 山田 さん。今日は 2026-02-04 です。
    }
}
Java

ここで押さえておきたいのは、「{0} が1番目の引数、{1} が2番目の引数」というルールです。
テンプレート側で順番を変えても、コード側は MessageFormat.format(template, arg0, arg1, ...) のままでよい、というのが強みです。

ただし、毎回 MessageFormat.format と書くのは少し長いですし、
null を渡したときの扱い、例外処理、ロケール指定などを毎回考えるのは面倒です。
ここを「いい感じに包む」のが簡易化ユーティリティの役目です。


一番シンプルな簡易ラッパーを作る

fmt("こんにちは {0}", "山田") のように短く書けるようにする

まずは「とにかく短く呼びたい」というニーズに応えるラッパーから始めます。

import java.text.MessageFormat;

public final class Msg {

    private Msg() {}

    public static String fmt(String pattern, Object... args) {
        if (pattern == null) {
            return null;
        }
        if (args == null || args.length == 0) {
            return pattern;
        }
        return MessageFormat.format(pattern, args);
    }
}
Java

使い方はこうなります。

String msg1 = Msg.fmt("こんにちは {0} さん", "山田");
String msg2 = Msg.fmt("合計は {0} 件です", 5);

System.out.println(msg1); // こんにちは 山田 さん
System.out.println(msg2); // 合計は 5 件です
Java

ここでの重要ポイントは三つあります。

一つ目は、「MessageFormat.format をそのまま呼ぶのではなく、fmt という短い名前にしている」ことです。
これだけで、ログやメッセージ生成のコードがかなり読みやすくなります。

二つ目は、「引数がないときはそのままパターンを返す」という挙動にしていることです。
これにより、「プレースホルダを含まない固定メッセージ」に対しても、
同じ Msg.fmt を使い回せます。

三つ目は、「パターンが null のときは null を返す」と決めていることです。
ここを例外にするか、"null" にするかは設計次第ですが、
まずは「入力が null なら出力も null」という素直なルールにしておくと扱いやすいです。


ロケールを意識した簡易化:数値や日付の書式も“おまかせ”にする

ロケール付きの MessageFormat をラップする

MessageFormat は、ロケールを指定すると、その国・地域に合わせた書式で数値や日付を整形できます。
例えば、カンマ区切りの位置や日付の並び順などが変わります。

ロケールを使う場合、素の書き方はこうなります。

import java.text.MessageFormat;
import java.util.Locale;

public class MessageFormatLocaleSample {

    public static void main(String[] args) {
        String pattern = "金額: {0,number}, 日付: {1,date}";

        MessageFormat jaFormat = new MessageFormat(pattern, Locale.JAPAN);
        MessageFormat usFormat = new MessageFormat(pattern, Locale.US);

        Object[] argsArr = {1234567, new java.util.Date()};

        System.out.println(jaFormat.format(argsArr));
        System.out.println(usFormat.format(argsArr));
    }
}
Java

これを毎回書くのはさすがに重いので、
ロケール付きの簡易ラッパーを用意します。

import java.text.MessageFormat;
import java.util.Locale;

public final class Msg {

    private Msg() {}

    public static String fmt(String pattern, Object... args) {
        if (pattern == null) {
            return null;
        }
        if (args == null || args.length == 0) {
            return pattern;
        }
        return MessageFormat.format(pattern, args);
    }

    public static String fmt(Locale locale, String pattern, Object... args) {
        if (pattern == null) {
            return null;
        }
        if (locale == null) {
            return fmt(pattern, args);
        }
        MessageFormat mf = new MessageFormat(pattern, locale);
        return mf.format(args);
    }
}
Java

使い方は次のようになります。

String pattern = "金額: {0,number}, 日付: {1,date}";

String ja = Msg.fmt(Locale.JAPAN, pattern, 1234567, new java.util.Date());
String us = Msg.fmt(Locale.US, pattern, 1234567, new java.util.Date());

System.out.println(ja);
System.out.println(us);
Java

ここでの重要ポイントは、「ロケール付きとロケールなしを同じクラスで扱えるようにしている」ことです。
普段は Msg.fmt("...") だけでよく、
「ロケールを意識したいときだけ Msg.fmt(locale, "...") を使う」というスタイルにできます。


例外時の扱いを決めておく:フォーマット失敗をどうするか

パターンミスや引数不足にどう対応するか

MessageFormat は、パターンが不正だったり、
引数の数や型が合わなかったりすると、思わぬ結果になったり例外が出たりします。

例えば、パターンに {0,number} と書いているのに、
文字列 "abc" を渡すと、フォーマットに失敗します。

簡易化ユーティリティでは、こうした「フォーマット失敗時の方針」を決めておくと安心です。
例えば、「失敗したらパターン+引数をそのまま返す」という緩い方針もありえます。

public static String safeFmt(String pattern, Object... args) {
    if (pattern == null) {
        return null;
    }
    if (args == null || args.length == 0) {
        return pattern;
    }
    try {
        return MessageFormat.format(pattern, args);
    } catch (IllegalArgumentException e) {
        // ログに出すなどしておいて…
        return pattern + " " + java.util.Arrays.toString(args);
    }
}
Java

ここでの重要ポイントは、「失敗したときに“沈黙しない”」ことです。
例外を握りつぶして空文字を返してしまうと、
画面やログに何も出ず、「なぜかメッセージが出ない」という謎の状態になります。

一方で、業務システムでは「画面を落とさない」ことも大事なので、
「例外はログに出しつつ、ユーザーには最低限の情報を見せる」という折衷案がよく使われます。
このあたりのバランスを、ユーティリティ側で決めておくと、
アプリ全体の挙動が揃ってきます。


i18nユーティリティと組み合わせると“本番仕様”になる

I18n.get("key") と Msg.fmt をつなげる

以前話した i18nユーティリティ(ResourceBundle からメッセージを取る仕組み)と、
この Msg.fmt を組み合わせると、実務でそのまま使える形になります。

例えば、こんな i18n ユーティリティがあるとします。

import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

public final class I18n {

    private static final String BUNDLE_NAME = "messages";

    private I18n() {}

    public static String get(Locale locale, String key, Object... args) {
        if (locale == null) {
            locale = Locale.getDefault();
        }
        try {
            ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
            String pattern = bundle.getString(key);
            if (args == null || args.length == 0) {
                return pattern;
            }
            return MessageFormat.format(pattern, args);
        } catch (MissingResourceException e) {
            return "!" + key + "!";
        }
    }
}
Java

これ自体がすでに「MessageFormat簡易化+i18n」の形になっています。
呼び出し側は、こう書くだけです。

Locale ja = Locale.JAPAN;

String msg = I18n.get(ja, "greeting", "山田");
// messages_ja.properties の greeting=こんにちは {0} さん
System.out.println(msg);
// → こんにちは 山田 さん
Java

ここでの重要ポイントは、「MessageFormat を直接触るのはユーティリティの中だけ」にしていることです。
業務コード側は、I18n.get(locale, key, args...) という“自分たちの言葉”だけを知っていればよく、
MessageFormat の細かい仕様を意識しなくて済みます。


まとめ:MessageFormat簡易化で身につけてほしい感覚

MessageFormat簡易化は、「強力だけど少し重い標準APIを、自分たちの現場にちょうどいい形にラップする」行為です。

ここで本当に身につけてほしいのは、次の感覚です。

そのまま使うと冗長なAPIは、ユーティリティで“呼びやすい顔”にしてしまってよい。
fmt(pattern, args...) のような短い窓口を用意すると、コードが一気に読みやすくなる。
ロケール付き・例外時の扱い・引数なしの扱いなど、「よくあるパターン」をユーティリティ側で決めておくと、アプリ全体の挙動が揃う。
i18n(ResourceBundle)と組み合わせると、「キー+引数」だけで多言語メッセージを安全に扱える。

もしあなたのコードのどこかに、

String msg = MessageFormat.format("こんにちは {0} さん", name);
Java

のような行が何度も出てきているなら、
それを一度 Msg.fmt("こんにちは {0} さん", name) のようなユーティリティに置き換えてみてください。

その小さな整理が、
「標準APIをそのまま使うだけの人」から
「標準APIを“自分たちの道具”に仕立て直せるエンジニア」への、確かな一歩になります。

タイトルとURLをコピーしました