Scalaの文字列フォーマット:format stringの使い方を解説

はい、承知いたしました。Scalaの文字列フォーマットにおけるformatメソッドの使い方について、詳細な解説を含む記事を作成します。約5000語を目指し、具体的な例を豊富に盛り込みます。


Scalaの文字列フォーマット:format stringの使い方を徹底解説

プログラミングにおいて、様々なデータ型の値を整形して文字列として出力する機会は非常に多いです。ログ出力、ユーザーへのメッセージ表示、ファイルへのデータ書き込みなど、その用途は多岐にわたります。Scalaには文字列を整形するためのいくつかの方法がありますが、その中でも特に強力で柔軟性が高いのが、Stringクラスのformatメソッドを使用する方法です。

このformatメソッドは、JavaのString.format(そしてその背後にあるjava.util.Formatterクラス)に基づいています。そのため、Javaで文字列フォーマットに慣れている方にとっては馴染み深い文法ですが、初めて触れる方にとってはやや複雑に感じられるかもしれません。しかし、一度その基本的なルールと、書式指定子(format specifier)の各要素が持つ意味を理解すれば、非常に洗練された、意図通りの文字列を自在に生成できるようになります。

本記事では、Scalaのformatメソッドの使い方について、その基本的な考え方から、書式指定子の詳細、様々なデータ型に対する適用例、フラグ、幅、精度といったオプション指定、さらには日付や時刻のフォーマット方法、ロケールの考慮点、他のScalaの文字列整形方法との比較に至るまで、網羅的にかつ詳細に解説していきます。この記事を読めば、あなたはScalaにおけるformatメソッドのエキスパートとなり、どのような複雑な文字列フォーマットの要求にも応えられるようになるでしょう。

1. なぜ文字列フォーマットが必要なのか?

まず、なぜわざわざ文字列フォーマットという仕組みが必要なのでしょうか。単純に値を文字列に変換して連結するだけでは不十分なのでしょうか?

例えば、整数x = 123と浮動小数点数y = 45.6789を組み合わせて、「The values are 123 and 45.6789」という文字列を作りたい場合を考えます。

単純な連結であれば、以下のように書けます。

scala
val x = 123
val y = 45.6789
val s1 = "The values are " + x + " and " + y
println(s1)
// 出力: The values are 123 and 45.6789

これはこれで正しい文字列が得られますが、いくつかの問題点があります。

  1. 可読性の低下: 連結する要素が増えたり、リテラル文字列と変数が混ざり合ったりすると、どのような最終的な文字列が生成されるのかを一見して理解するのが難しくなります。特に長い文字列や多くの変数を含む場合、コードが読みにくくなります。
  2. 整形が難しい: 浮動小数点数の表示桁数を制限したい、数値を特定の幅で表示したい、先頭をゼロ埋めしたい、といった整形要求が出てきた場合、単純な連結だけでは対応できません。例えば、yを小数点以下2桁で表示したい場合、別途yを整形するためのコードを書く必要があります。
  3. 型の扱い: 様々な型の値を文字列に変換して連結する場合、それぞれの型が持つデフォルトのtoStringメソッドの挙動に依存します。特定の形式(例えば16進数や指数形式)で表示したい場合には、追加の変換が必要です。
  4. 国際化/地域化 (i18n/l10n): 数値の区切り文字(小数点や桁区切り)や日付/時刻の表示形式は、国や地域(ロケール)によって異なります。単純な連結では、これらのロケールに応じた表示を自動的に行うことができません。

文字列フォーマットの仕組みは、これらの問題点を解決するためにあります。単一の書式文字列の中に、出力したいリテラル文字列と、値を埋め込むための「プレースホルダー(書式指定子)」を記述することで、可読性を保ちつつ、値の型に応じた変換や、幅、精度、フラグによる詳細な整形を一箇所で行えるようになります。

2. ScalaにおけるString.formatの基本

ScalaのString.formatメソッドは、Javaのjava.lang.String.formatメソッドをラップしています。したがって、その機能や書式指定子の文法はJavaのものと同一です。基本的な使い方は以下のようになります。

scala
val formatString = "リテラル文字列と%sのような書式指定子を組み合わせる"
val result = formatString.format(値1, 値2, ...)

または、以下のようにStringコンパニオンオブジェクトのメソッドとしても使用できます。

scala
val result = String.format("リテラル文字列と%sのような書式指定子を組み合わせる", 値1, 値2, ...)

多くの場合、最初の「書式文字列オブジェクトのメソッドとして呼び出す」形式が使われます。

書式文字列の中には、通常の文字と、%文字で始まる「書式指定子(format specifier)」が含まれます。書式指定子は、formatメソッドに渡された引数のどの値を、どのような形式で文字列に変換して埋め込むかを指定します。

例を見てみましょう。

“`scala
val name = “Scala”
val version = 2.13

// 書式指定子 %s と %f を使用
val message = “Hello, %s! Version: %.2f”.format(name, version)
println(message)
// 出力: Hello, Scala! Version: 2.13
“`

この例では、
* %s は、対応する引数(name)を文字列としてフォーマットすることを指定します。
* %.2f は、対応する引数(version)を浮動小数点数としてフォーマットし、小数点以下2桁で表示することを指定します。

formatメソッドの引数は、書式文字列中に現れる書式指定子の順番に対応します(後述する引数インデックスを使わない場合)。最初の書式指定子 %s には最初の引数 name が、次の書式指定子 %.2f には次の引数 version が対応します。

書式指定子以外の部分は、そのまま出力されるリテラル文字列です。

3. 書式指定子 (Format Specifier) の構造

書式指定子は、%文字で始まり、通常は以下のような構造を持ちます。

%[引数インデックス$][フラグ][幅][.精度]変換文字

この中で必須なのは、%文字と「変換文字」だけです。他の部分はオプションであり、特定のフォーマット要求に応じて追加します。それぞれの要素について詳しく見ていきましょう。

  • %: 書式指定子の始まりを示す必須の文字です。
  • [引数インデックス$]: オプション。フォーマットする引数が、formatメソッドに渡された引数のリストの中で何番目であるかを指定します。1$は最初の引数、2$は2番目の引数、のように指定します。これを省略した場合、書式指定子は書式文字列中に現れる順番に引数と対応します。%<という特別な指定子もあり、これは「直前の書式指定子と同じ引数」を意味します。
  • [フラグ]: オプション。出力の形式を修飾する1つ以上の文字です(例: ゼロ埋め、符号表示、桁区切りなど)。
  • [幅]: オプション。出力される文字列の最小の文字数を指定します。指定した幅より短い場合は、デフォルトでは空白でパディングされます。
  • [.精度]: オプション。変換文字の種類によって意味が異なります(例: 浮動小数点数の小数点以下の桁数、文字列の最大文字数)。ピリオド.の後に整数値を指定します。
  • 変換文字: 必須。引数をどのような形式で(文字列、整数、浮動小数点数、日付など)変換するかを指定する文字です。

これらの要素は、指定する順序が決まっています。例: %+10.2f の場合:
* %: 開始
* +: フラグ(常に符号を表示)
* 10: 幅(最低10文字)
* .2: 精度(小数点以下2桁)
* f: 変換文字(浮動小数点数)

それでは、それぞれの要素について、具体的な例を交えながら詳しく解説していきます。

3.1. 変換文字 (Conversion Characters)

変換文字は、書式指定子の中で最も重要な要素であり、対象となる引数の型と、それをどのように文字列に変換するかを決定します。主な変換文字には以下のようなものがあります。

3.1.1. 一般的な変換 (General Conversions)

任意の引数の型に適用できることが多い変換です。

  • s, S: 引数を文字列に変換します。引数がnullの場合は"null"を出力します。sは引数のtoString()メソッドの結果を使用します。Sは大文字に変換します(Stringの場合は効果がないことが多いですが、他の型の場合は結果が大文字になります)。

    “`scala
    val obj1: Any = 12345
    val obj2: Any = null
    val obj3: Any = true

    println(“s: %s, %s, %s”.format(obj1, obj2, obj3))
    // 出力: s: 12345, null, true
    println(“S: %S, %S, %S”.format(obj1, obj2, obj3))
    // 出力: S: 12345, NULL, TRUE
    “`

  • h, H: 引数のハッシュコードを16進数で変換します。nullの場合は"null"を出力します。hは小文字、Hは大文字の16進数を使用します。

    “`scala
    val data1 = “Scala”
    val data2 = List(1, 2, 3)
    val data3 = null

    println(“h: %h, %h, %h”.format(data1, data2, data3))
    // 出力例 (ハッシュコードは実行環境で変わる可能性があります): h: 4b149582, e0b330d1, null
    println(“H: %H, %H, %H”.format(data1, data2, data3))
    // 出力例: H: 4B149582, E0B330D1, NULL
    “`

  • b, B: 引数を真偽値として変換します。nullの場合は"false"を出力します。それ以外の値は、nullでない限り常に"true"を出力します。bは小文字、Bは大文字を使用します。

    “`scala
    val bool1 = true
    val bool2 = false
    val obj = “Scala”
    val nul = null

    println(“b: %b, %b, %b, %b”.format(bool1, bool2, obj, nul))
    // 出力: b: true, false, true, false
    println(“B: %B, %B, %B, %B”.format(bool1, bool2, obj, nul))
    // 出力: B: TRUE, FALSE, TRUE, FALSE
    ``
    **注意:**
    %b%Bは引数の型を問わず、nullでないかどうかで真偽を判断します。明示的にBoolean`型の引数を使うのが一般的です。

3.1.2. 文字変換 (Character Conversions)
  • c, C: 引数をUnicode文字に変換します。Char, Byte, Short, Int型の引数を受け付けます。cは小文字、Cは大文字に変換しようとしますが、基本的には文字そのものなので大文字/小文字の区別はあまり意味を持ちません(ロケールによっては影響する可能性はあります)。

    scala
    println("char: %c".format('A'))
    // 出力: char: A
    println("int: %c".format(65)) // ASCII/Unicode code point for 'A'
    // 出力: int: A
    println("Char capital: %C".format('b'))
    // 出力: Char capital: B

3.1.3. 数値変換 (Numeric Conversions)

整数型、浮動小数点数型、BigInt, BigDecimalなどに適用できます。

  • d: 引数を10進数の整数に変換します。Byte, Short, Int, Long, BigIntなどの整数型を受け付けます。

    “`scala
    val i = 12345
    val l = 9876543210L
    val bi = BigInt(“12345678901234567890”)

    println(“d: %d, %d, %d”.format(i, l, bi))
    // 出力: d: 12345, 9876543210, 12345678901234567890
    “`

  • o: 引数を8進数の整数に変換します。Byte, Short, Int, Long, BigIntなどの整数型を受け付けます。

    scala
    val i = 123
    println("o: %o".format(i))
    // 出力: o: 173 (123 in base 8)

  • x, X: 引数を16進数の整数に変換します。Byte, Short, Int, Long, BigIntなどの整数型を受け付けます。xは小文字、Xは大文字の16進数を使用します。

    scala
    val i = 255
    val l = 4294967295L // 2^32 - 1
    println("x: %x, %x".format(i, l))
    // 出力: x: ff, ffffffff
    println("X: %X, %X".format(i, l))
    // 出力: X: FF, FFFFFFFF

  • f: 引数を10進数の浮動小数点数に変換します。Float, Double, BigDecimalなどの浮動小数点数型を受け付けます。デフォルトでは小数点以下6桁が表示されます。精度を指定しない場合、デフォルトの精度は6です。

    scala
    val d = 123.4567890123
    val bd = BigDecimal("9876543210.123456789")
    println("f (default precision): %f, %f".format(d, bd))
    // 出力: f (default precision): 123.456789, 9876543210.123457

  • e, E: 引数を指数(科学)表記の浮動小数点数に変換します。eは指数部のeが小文字、Eは大文字になります。デフォルトの精度は6です。

    scala
    val d = 1234567890.123
    println("e: %e".format(d))
    // 出力: e: 1.234568e+09
    println("E: %E".format(d))
    // 出力: E: 1.234568E+09

  • g, G: 引数を一般的な浮動小数点数形式に変換します。値の大きさに応じて、%fまたは%e(または%F/%E)の形式が自動的に選択されます。精度は有効桁数を指定します。

    scala
    val d1 = 123.456789 // %f 形式が選ばれやすい
    val d2 = 1234567890.123 // %e 形式が選ばれやすい
    println("g: %g, %g".format(d1, d2))
    // 出力: g: 123.457, 1.23457e+09 (精度デフォルト6の場合)

  • a, A: 引数を16進数の浮動小数点数に変換します。aは小文字、Aは大文字を使用します。

    scala
    val d = 12.345
    println("a: %a".format(d))
    // 出力: a: 0x1.8ae147ae147aep3
    println("A: %A".format(d))
    // 出力: A: 0X1.8AE147AE147AEP3

3.1.4. 日付/時刻変換 (Date/Time Conversions)

日付または時刻の値(java.util.Date, java.util.Calendar, java.time._ パッケージのクラスなど)を特定の形式で表示します。日付/時刻変換は、変換文字tまたはTの後に、表示したい日付/時刻の要素を指定する別の変換文字が続きます。

書式指定子: %t[フラグ][幅]変換文字 または %T[フラグ][幅]変換文字

日付/時刻の変換文字(tまたはTの後に続く文字)には非常に多くの種類があります。主なものをいくつか紹介します。

時間要素:
* H: 時 (00 – 23)
* I: 時 (01 – 12)
* k: 時 (0 – 23)
* l: 時 (1 – 12)
* M: 分 (00 – 59)
* S: 秒 (00 – 60)
* L: ミリ秒 (000 – 999)
* N: ナノ秒 (000000000 – 999999999)
* p: 午前/午後 (am/pm)
* z: タイムゾーンからのオフセット (+HHMMまたは-HHMM)
* Z: タイムゾーンの省略名

日付要素:
* Y: 年 (4桁)
* y: 年の最後の2桁 (00 – 99)
* B: 月のフルネーム (January, February, …)
* b または h: 月の省略名 (Jan, Feb, …)
* m: 月 (01 – 12)
* d: 日 (01 – 31)
* e: 日 (1 – 31)
* A: 曜日のフルネーム (Sunday, Monday, …)
* a: 曜日の省略名 (Sun, Mon, …)
* j: 年中での通算日 (001 – 366)

よく使われる組み合わせ:
* F: ISO 8601形式の日付 (yyyy-mm-dd)
* D: US形式の日付 (mm/dd/yy)
* T: 24時間形式の時刻 (HH:MM:SS)
* r: 12時間形式の時刻 (hh:mm:ss am/pm)
* c: 日付と時刻 (例: Mon Jan 01 15:00:00 CEST 2000)

例を見てみましょう。java.util.Dateを使用します。Scala 2.13+ では java.time パッケージの利用が推奨されますが、formatメソッドはjava.utilの日付/時刻型や Long (ミリ秒単位のタイムスタンプ) を引数として受け付けることが多いです(内部でCalendarDateに変換されるため)。Java 8以降のjava.timeクラスを使用する場合は、明示的にjava.util.Dateなどに変換するか、適切なラッパーを使用する必要がある場合がありますが、多くの場合はそのまま引数に渡せます。ここでは簡潔にjava.util.Dateを使用します。

“`scala
import java.util.Date

val now = new Date()

// 現在の日付と時刻を表示
println(“Full date and time: %tc”.format(now))
// 出力例: Full date and time: Wed Oct 26 10:30:00 JST 2023 (ロケールによる)

// ISO 8601 形式の日付と 24時間形式の時刻
println(“ISO Date: %tF, Time: %tT”.format(now, now))
// 出力例: ISO Date: 2023-10-26, Time: 10:30:00 (ロケールによる)

// 年、月、日を個別に
println(“Year: %tY, Month: %tm, Day: %td”.format(now, now, now))
// 出力例: Year: 2023, Month: 10, Day: 26

// 曜日と月の名前
println(“Day of week: %tA, Month name: %tB”.format(now, now))
// 出力例: Day of week: Wednesday, Month name: October (ロケールによる)

// 12時間形式と午前/午後
println(“Time (12-hour): %tl:%tM %tp”.format(now, now, now))
// 出力例: Time (10:30 am) (ロケールによる)
“`
注意: 日付/時刻フォーマットはロケールに強く依存します。上記出力例は日本のロケールに基づいています。異なるロケールの場合は、月や曜日の名前、午前/午後の表記などが変わります。ロケールを指定する方法は後述します。

日付/時刻変換は、同じ引数に対して複数の書式指定子を適用することがよくあります。例えば、年、月、日を順番に表示する場合、引数(日付オブジェクト)を3回指定する必要があります。引数インデックスを使うと、これをより簡潔に書けます。

“`scala
import java.util.Date

val now = new Date()

// 引数インデックスを使用して同じ ‘now’ オブジェクトを参照
println(“Date: %1$tY-%1$tm-%1$td”.format(now))
// 出力例: Date: 2023-10-26
``
ここで、
%1$tYは「最初の引数(1$)を年(tY)としてフォーマット」、%1$tmは「最初の引数(1$)を月(tm)としてフォーマット」、%1$tdは「最初の引数(1$)を日(td`)としてフォーマット」という意味になります。

3.1.5. その他の変換
  • %: リテラルな%文字を出力します。format文字列内で%文字自体を表示したい場合は、%%と書く必要があります。

    scala
    println("Percentage: %%".format())
    // 出力: Percentage: %

  • n: プラットフォームに依存する行末区切り文字(改行コード)を出力します。これはSystem.lineSeparator()と同じです。\nを使うこともできますが、%nを使う方がプラットフォームに依存しない改行を生成できます。

    scala
    println("Line 1%nLine 2".format())
    // 出力:
    // Line 1
    // Line 2

3.2. 引数インデックス (Argument Index)

デフォルトでは、書式指定子は書式文字列中の出現順にformatメソッドの引数と対応します。

scala
println("%s %s".format("Hello", "World"))
// 出力: Hello World

しかし、同じ引数を複数回使用したい場合や、引数の順番を書式指定子の出現順と変えたい場合があります。このような場合に引数インデックスを使用します。書式指定子の%の直後にN$の形式で指定します。Nは1から始まる引数の番号です。

“`scala
// 引数の順番を入れ替える
println(“%2$s %1$s”.format(“World”, “Hello”))
// 出力: Hello World

// 同じ引数を複数回使う
println(“Value: %1$d, Value again: %1$d”.format(123))
// 出力: Value: 123, Value again: 123
“`

さらに便利なのが、<というインデックスです。これは、「直前に使用された引数」を再利用するという意味です。

“`scala
// ‘<‘ を使って直前の引数を再利用
println(“Value: %d, Same value: %<d”.format(123))
// 出力: Value: 123, Same value: 123

// ‘<‘ は直前の書式指定子が参照した引数を参照する
println(“First: %s, Second: %s, Third: %<s”.format(“A”, “B”, “C”))
// 出力: First: A, Second: B, Third: B (Third は Second の引数 ‘B’ を参照)

println(“First: %s, Second: %s, Third: %3$s”.format(“A”, “B”, “C”))
// 出力: First: A, Second: B, Third: C (Third は 3$ で明示的に ‘C’ を参照)
``<`インデックスはコードを簡潔にするのに役立ちますが、可読性を損なわないように注意して使用する必要があります。特に複雑な書式文字列で多用すると、どの引数が使われているのか分かりにくくなることがあります。

3.3. フラグ (Flags)

フラグは、出力の表示形式を詳細に制御するための修飾子です。書式指定子のオプション部分であり、引数インデックスの後に続きます。複数のフラグを組み合わせて指定することも可能です(ただし、組み合わせによっては無効なものや意味のないものもあります)。主なフラグとその効果を、適用可能な変換文字とともに見ていきましょう。

フラグ 意味 適用可能な変換文字 出力例 (引数 123, -123)
- 出力をフィールドの左端に寄せる(右寄せではなくなる)。幅指定が必要。 すべての変換文字 %-10s, %-10d -123
+ 数値の場合、正の値には+、負の値には-を常に表示する。 d, o, x, X, e, E, f, g, G, a, A %+d, %+f +123, -123
数値の場合、正の値には先頭に空白文字を追加し、負の値には-を表示する。 d, o, x, X, e, E, f, g, G, a, A % d 123, -123
0 数値の場合、指定された幅まで先頭をゼロで埋める。幅指定が必要。-フラグと同時に使用不可。 d, o, x, X, e, E, f, g, G, a, A %010d, %010.2f 0000000123
, 10進数形式で、ロケールに応じた桁区切り文字を追加する。 d, e, E, f, g, G (数値型) %,d, %,f 123,456, 123,456.78
( 負の数値を括弧で囲む。 d, e, E, f, g, G (数値型) %(d (123) (負数の場合のみ)
# 変換文字に固有の代替形式を使用する。 o, x, X, e, E, g, G, a, A %#o, %#x, %#f, %#g %#o: 0173, %#x: 0xff, %#f: 1.230000 (精度デフォルト)

具体的な例を見ていきましょう。

“`scala
val number = 123
val negNumber = -123
val pi = math.Pi // 約 3.14159…

// ‘-‘ フラグ: 左寄せ (幅指定が必要)
println(“Left aligned string: ‘%-10s'”.format(“Scala”))
// 出力: Left aligned string: ‘Scala ‘
println(“Left aligned int: ‘%-10d'”.format(number))
// 出力: Left aligned int: ‘123 ‘

// ‘+’ フラグ: 符号表示
println(“Sign plus: %+d, %+d”.format(number, negNumber))
// 出力: Sign plus: +123, -123
println(“Sign plus float: %+f, %+f”.format(pi, -pi))
// 出力: Sign plus float: +3.141593, -3.141593

// ‘ ‘ フラグ: 正数にスペース
println(“Space flag: % d, % d”.format(number, negNumber))
// 出力: Space flag: 123, -123

// ‘0’ フラグ: ゼロ埋め (幅指定が必要、’-‘ とは併用不可)
println(“Zero padding: %010d”.format(number))
// 出力: Zero padding: 0000000123
println(“Zero padding float: %010.2f”.format(pi))
// 出力: Zero padding float: 000003.14

// ‘,’ フラグ: 桁区切り (ロケールによる)
println(“Comma separator (Int): %,d”.format(1234567))
// 出力例 (日本ロケール): Comma separator (Int): 1,234,567
println(“Comma separator (Float): %,f”.format(1234567.89))
// 出力例 (日本ロケール): Comma separator (Float): 1,234,567.890000

// ‘(‘ フラグ: 負数を括弧で囲む
println(“Parentheses for negative: %(d, %(d”.format(number, negNumber))
// 出力: Parentheses for negative: 123, (123)

// ‘#’ フラグ: 代替形式
println(“Alternate octal: %#o”.format(123))
// 出力: Alternate octal: 0173
println(“Alternate hex: %#x”.format(255))
// 出力: Alternate hex: 0xff
println(“Alternate float: %#f”.format(1.2)) // 小数点以下が0の場合も表示
// 出力: Alternate float: 1.200000
println(“Alternate general (g): %#g”.format(123.0)) // 小数点以下が表示される
// 出力例: Alternate general (g): 123.000

// 複数のフラグの組み合わせ
println(“Multiple flags: %+010d”.format(number))
// 出力: Multiple flags: +000000123
println(“Multiple flags: %,-15.2f”.format(123456.789))
// 出力例 (日本ロケール): Multiple flags: 123,456.79
“`

フラグは変換文字によって適用できるものが異なります。上記の表と例を参考に、必要な整形に対応するフラグを選択してください。

3.4. 幅 (Width)

幅は、出力される文字列の最小の文字数を指定します。書式指定子のオプション部分であり、フラグの後に続きます。整数値を指定します。

指定された幅よりも変換結果の文字列が短い場合、デフォルトでは文字列の左側に空白がパディング(埋め合わせ)されて、指定された幅になります。

“`scala
val value = 42

println(“Width 5: ‘%5d'”.format(value))
// 出力: Width 5: ‘ 42’ (左に3つの空白)
println(“Width 5 string: ‘%5s'”.format(“Hi”))
// 出力: Width 5 string: ‘ Hi’ (左に3つの空白)
“`

-フラグを同時に指定すると、出力は左寄せになり、パディングは右側に行われます。

scala
println("Width 5, left align: '%-5d'".format(value))
// 出力: Width 5, left align: '42 ' (右に3つの空白)
println("Width 5 string, left align: '%-5s'".format("Hi"))
// 出力: Width 5 string, left align: 'Hi ' (右に3つの空白)

数値変換 (d, o, x, f, eなど) では、0フラグを同時に指定することで、空白ではなく先頭をゼロで埋めることができます。0フラグは-フラグとは同時に使用できません。

scala
println("Width 5, zero padding: '%05d'".format(value))
// 出力: Width 5, zero padding: '00042' (左に3つのゼロ)
println("Width 10, zero padding float: '%010.2f'".format(math.Pi))
// 出力: Width 10, zero padding float: 0000003.14 (左に6つのゼロ)

指定された幅よりも変換結果の文字列が長い場合は、指定された幅は無視され、文字列全体が表示されます。

scala
println("Width 3, value 12345: '%3d'".format(12345))
// 出力: Width 3, value 12345: '12345' (幅は無視される)

幅の指定は、特に表形式の出力などで、列を揃えたい場合に非常に役立ちます。

3.5. 精度 (Precision)

精度は、書式指定子のオプション部分であり、ピリオド.の後に整数値を指定します。精度が持つ意味は、使用する変換文字によって大きく異なります。

3.5.1. 浮動小数点数変換 (f, e, E, g, G, a, A)

浮動小数点数変換の場合、精度は小数点以下の桁数を指定します。デフォルトでは6桁です。

“`scala
val pi = math.Pi // 約 3.1415926535…

println(“Precision .2f: %.2f”.format(pi))
// 出力: Precision .2f: 3.14
println(“Precision .4f: %.4f”.format(pi))
// 出力: Precision .4f: 3.1416 (四捨五入される)
println(“Precision .0f: %.0f”.format(pi))
// 出力: Precision .0f: 3 (四捨五入される)
“`

指数変換 (e, E) でも同様に小数点以下の桁数を指定します。

“`scala
val largeNum = 123456789.12345

println(“Precision .2e: %.2e”.format(largeNum))
// 出力: Precision .2e: 1.23e+08
“`

一般的な浮動小数点数変換 (g, G) の場合、精度は有効桁数を指定します。

scala
println("Precision .2g (f-like): %.2g".format(123.456))
// 出力: Precision .2g (f-like): 1.2e+02 (有効数字2桁になるようにe形式が選ばれた)
println("Precision .5g (f-like): %.5g".format(123.456))
// 出力: Precision .5g (f-like): 123.46 (有効数字5桁になるようにf形式が選ばれた)
println("Precision .5g (e-like): %.5g".format(123456.789))
// 出力: Precision .5g (e-like): 1.2346e+05 (有効数字5桁になるようにe形式が選ばれた)

3.5.2. 文字列変換 (s, S)

文字列変換の場合、精度は出力される文字列の最大文字数を指定します。元の文字列が精度で指定した文字数より長い場合は、指定した文字数で切り詰められます。短い場合はそのまま出力されます(パディングは幅で行う)。

“`scala
val longString = “This is a long string.”

println(“Precision .10s: %.10s”.format(longString))
// 出力: Precision .10s: This is a
println(“Precision .30s: %.30s”.format(longString))
// 出力: Precision .30s: This is a long string. (元の文字列より長いので切り詰められない)
“`

3.5.3. 整数変換 (d, o, x, X)

整数変換の場合、精度は出力される桁数の最小値を指定します。元の数値の桁数が精度で指定した値より短い場合は、先頭がゼロで埋められます。長い場合は無視されます。精度を指定しない場合のデフォルトは1です。

“`scala
val num = 123

println(“Precision .5d: %.5d”.format(num))
// 出力: Precision .5d: 00123 (左に2つのゼロ)
println(“Precision .2d: %.2d”.format(num))
// 出力: Precision .2d: 123 (元の桁数より短いので無視)

// 幅と精度の組み合わせ
println(“Width 10, Precision .5d: %10.5d”.format(num))
// 出力: Width 10, Precision .5d: 00123 (精度でゼロ埋め後、幅で空白パディング)
“`

-フラグがある場合は、精度によるゼロ埋めはされず、幅による空白パディングのみになります。

scala
println("Width 10, Precision .5d, left align: %-10.5d".format(num))
// 出力: Width 10, Precision .5d, left align: 00123

3.5.4. その他の変換

他の変換文字 (b, B, c, C, h, H, 日付/時刻変換) では、通常、精度を指定することはできません。指定しても無視されるか、例外が発生する可能性があります。

3.6. ロケール (Locale)

数値の桁区切り文字や小数点文字、日付や時刻の表示形式、曜日の名前や月の名前などは、国や地域(ロケール)によって異なります。デフォルトでは、Scala (Java) は実行環境のデフォルトロケールを使用します。

しかし、特定のロケールでフォーマットしたい場合や、デフォルトロケールとは異なる形式で表示したい場合があります。その場合は、formatメソッドの最初の引数にjava.util.Localeオブジェクトを指定します。

String.format(locale, formatString, args...)

“`scala
import java.util.Locale
import java.util.Date

val number = 1234567.89
val now = new Date()

// デフォルトロケール (例: 日本)
println(“Default: %,f”.format(number))
// 出力例: Default: 1,234,567.890000

// 英語 (US) ロケール
println(“US: %,f”.format(Locale.US, number))
// 出力例: US: 1,234,567.890000 (桁区切りは同じだが、小数点文字は ‘.’)

// フランス語ロケール (小数点に ‘,’ を使用することがある)
println(“France: %,f”.format(Locale.FRANCE, number))
// 出力例: France: 1 234 567,890000 (スペースやカンマが桁区切り、カンマが小数点)

// 日本語ロケールでの日付
println(“Date (Japan): %tc”.format(Locale.JAPAN, now))
// 出力例: Date (Japan): 2023/10/26(木) 10:30:00 (ロケールによる)

// 英語 (US) ロケールでの日付
println(“Date (US): %tc”.format(Locale.US, now))
// 出力例: Date (US): Thu Oct 26 10:30:00 JST 2023 (ロケールによる)

// ドイツ語ロケールでの日付
println(“Date (Germany): %tc”.format(Locale.GERMANY, now))
// 出力例: Date (Germany): Do 26. Okt 10:30:00 JST 2023 (ロケールによる)
“`
ロケールを指定することで、数値や日付/時刻の表示形式を国際的な規約や特定の地域の慣習に合わせることができます。これは、多言語対応のアプリケーションを作成する上で非常に重要です。

よく使うロケールはLocaleクラスの定数として定義されています (Locale.US, Locale.JAPAN, Locale.GERMANY, Locale.FRANCE, Locale.ENGLISHなど)。特定の言語や国を指定したい場合は、new Locale("言語コード")new Locale("言語コード", "国コード")のようにしてLocaleオブジェクトを作成します。

4. 様々なデータ型とformatメソッド

formatメソッドは、Scalaのプリミティブ型 (Int, Long, Double, Boolean, Charなど) はもちろん、Javaの対応するラッパークラス (Integer, Long, Double, Boolean, Character)、String, BigInt, BigDecimal, java.util.Date, java.util.Calendar, java.timeパッケージのクラス、nullなど、様々な型の値を引数として受け付けることができます。

内部的には、formatメソッドは引数の型と書式指定子の変換文字を見て、適切なフォーマット処理を行います。例えば、%dIntを渡せばそのまま10進数として処理され、%sDoubleを渡せばdoubleValue.toString()の結果が使用されます。

型と変換文字の組み合わせが不正な場合は、java.util.IllegalFormatExceptionやそのサブクラスの例外(MissingFormatArgumentException, UnknownFormatConversionException, IllegalFormatConversionExceptionなど)が発生します。

scala
// 例: 不正な組み合わせ
// "%d".format("Hello") // IllegalFormatConversionException: d != java.lang.String
// "%.2f".format(123) // IllegalFormatConversionException: f != java.lang.Integer (浮動小数点数にしか精度は適用できない)

特に精度やフラグは、適用可能な変換文字が決まっていますので注意が必要です。

5. Scalaにおける他の文字列整形方法との比較

Scalaにはformatメソッド以外にも文字列を整形する方法がいくつかあります。それぞれの特徴を理解し、状況に応じて適切な方法を選択することが重要です。

  • 文字列連結 (+):

    • 最も基本的で単純な方法。
    • 簡単な連結には向いているが、複雑な整形や多くの変数を含む場合は可読性が低い。
    • ロケールによる整形や幅/精度の指定はできない。

    scala
    val x = 10
    val y = 20
    val s = "Sum is: " + (x + y) // 単純な連結

  • s-内挿子 (String Interpolation with s):

    • Scala 2.10で導入された、可読性の高い方法。
    • 文字列リテラルの前にsを付け、文字列中に${変数名}または$変数名の形式で変数を埋め込む。
    • toStringメソッドが呼ばれるため、基本的な型の変換は自動で行われる。
    • 幅や精度などの詳細な整形は直接はできない。

    scala
    val name = "Scala"
    val version = 2.13
    val s = s"Hello, $name! Version: $version" // s-内挿子

  • f-内挿子 (String Interpolation with f):

    • Scala 2.10で導入された、formatメソッドの書式指定子を使える内挿子。
    • 文字列リテラルの前にfを付け、書式指定子を$変数名%書式指定子の形式で記述する。
    • formatメソッドに近い機能を持つが、書式指定子は各変数に直接紐づく。
    • 引数インデックスは基本的に使用しない(書式指定子と変数が1対1で対応するため)。
    • formatよりも簡潔に書けることが多い。

    scala
    val name = "Scala"
    val version = 2.13
    val message = f"Hello, $name%s! Version: $version%.2f" // f-内挿子

    これは、"Hello, %s! Version: %.2f".format(name, version) とほぼ同等の結果になります。

  • raw-内挿子 (String Interpolation with raw):

    • Scala 2.10で導入された、エスケープシーケンス(\n, \tなど)を処理しない内挿子。
    • 主に、正規表現やWindowsのパスなど、バックスラッシュをそのまま扱いたい場合に便利。
    • 整形機能は持たない。

    scala
    val path = "C:\\Program Files\\Scala"
    println(raw"The path is: $path") // \ をそのまま表示
    // 出力: The path is: C:\Program Files\Scala

  • printfメソッド:

    • java.io.PrintStreamjava.io.PrintWriter(コンソール出力のprintlnprintfが含まれる)のメソッド。
    • formatメソッドと同じ書式指定子を使用する。
    • 文字列を生成して返すのではなく、直接出力ストリームに書き込む。
    • formatと同じJava由来の機能であり、用途に応じて使い分ける。

    scala
    printf("Hello, %s! Version: %.2f%n", "Scala", 2.13) // printf で直接出力
    // 出力: Hello, Scala! Version: 2.13

formatメソッドとf-内挿子の使い分け:

多くの場合、f-内挿子はformatメソッドよりも簡潔で読みやすく、簡単な整形にはf-内挿子を使うのが良いでしょう。

しかし、formatメソッドがより適している、あるいは必須となるケースもあります。

  1. 実行時にフォーマット文字列を動的に決定する場合: f-内挿子はコンパイル時に文字列リテラルが固定されている必要がありますが、formatメソッドはフォーマット文字列をString型の変数として持つことができます。
  2. 同じフォーマット文字列を繰り返し使用する場合: フォーマット文字列を変数や定数として定義しておき、formatメソッドを複数回呼び出すことで、コードの重複を避けることができます。
  3. 複雑な引数インデックスの操作が必要な場合: f-内挿子では引数インデックスや<インデックスは使えません(または非常に限定的です)。同じ引数を複数回参照したり、引数の順番を入れ替えたりする必要がある場合は、formatメソッドが適しています。
  4. Javaとの連携: JavaのAPIがString.formatで生成された文字列を期待している場合など、JavaのFormatterの厳密な仕様に合わせる必要がある場合にformatが有用です。

基本的には、単純な埋め込みや簡単な整形ならsf内挿子、動的なフォーマットや複雑な引数参照、厳密なJava互換性が必要ならformatメソッド、直接コンソールなどに出力するならprintfと使い分けるのが良いプラクティスと言えるでしょう。

6. formatメソッド使用上の注意点とベストプラクティス

  • 例外の可能性: 引数の数と書式指定子の数が一致しない、または引数の型が書式指定子と互換性がない場合など、IllegalFormatException系の実行時例外が発生する可能性があります。特に、ユーザーからの入力などをフォーマットする場合は、入力の検証や例外処理を適切に行う必要があります。
  • ロケール: デフォルトロケールが常に期待通りであるとは限りません。特に数値や日付/時刻を扱う場合は、必要に応じて明示的にロケールを指定することをおすすめします。Locale.ROOTはロケールに依存しない最もシンプルなフォーマット(桁区切りなし、小数点に.など)を提供します。
  • パフォーマンス: 非常に大量の文字列を繰り返しフォーマットする場合、formatメソッド(やf-内挿子)はオブジェクト生成などのオーバーヘッドが発生します。パフォーマンスがクリティカルな場面では、別の効率的な方法(例えばStringBuilderを使った手動での構築など)を検討する必要があるかもしれません。ただし、ほとんどのアプリケーションではformatのパフォーマンスで十分です。
  • 可読性: 複雑な書式指定子は読みにくくなることがあります。必要に応じて書式文字列を複数の小さな部分に分割したり、コメントで補足したりすることを検討しましょう。特に、多数のフラグや幅、精度を組み合わせる場合は注意が必要です。
  • セキュリティ: ユーザーからの入力を直接フォーマット文字列として使用することは避けてください。悪意のある入力によって予期しない動作やエラーを引き起こす可能性があります。フォーマット文字列は信頼できるソース(コード内の定数など)から取得するようにしてください。

7. まとめ

本記事では、ScalaのString.formatメソッドについて、その詳細な使い方を解説しました。

  • formatメソッドはJavaのString.formatに基づいた強力な文字列整形機能です。
  • 書式指定子は%[引数インデックス$][フラグ][幅][.精度]変換文字という構造を持ちます。
  • %と変換文字は必須です。
  • 変換文字は、引数をどのように(文字列、整数、浮動小数点数、日付など)表示するかを決定します(s, d, f, tYなど)。
  • 引数インデックス (N$, <) を使うことで、引数の参照順序や再利用を制御できます。
  • フラグ (-, +, , 0, ,, (, #) を使うことで、符号表示、パディング、桁区切りなどの詳細な整形が可能です。
  • 幅は出力の最小文字数を指定し、デフォルトでは左寄せ、-フラグで右寄せ、0フラグでゼロ埋めができます。
  • 精度は、変換文字の種類によって意味が異なります(浮動小数点数なら小数点以下の桁数、文字列なら最大文字数、整数なら最小桁数)。
  • ロケールを指定することで、数値や日付/時刻の地域に応じた表示が可能です。
  • Scalaにはs, f, raw内挿子やprintfといった他の整形方法もあり、formatメソッドは動的なフォーマットや複雑な引数参照、Java連携などに特に強みを発揮します。

formatメソッドの書式指定子は一見複雑ですが、それぞれの要素の役割を理解し、豊富な変換文字やオプションを使いこなすことで、あなたの望む通りの文字列を自在に生成できるようになります。ぜひ、この記事で学んだ知識を活かして、あなたのScalaプログラムで効率的かつ正確な文字列フォーマットを実現してください。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール