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を“自分たちの道具”に仕立て直せるエンジニア」への、確かな一歩になります。
