Kotlin の文字列操作は、String 型に用意された関数を「取り出す・分ける・つなぐ・置き換える・整える・調べる・変換する」の7つの目的で引くと、必要なものがすぐ見つかります。 たとえば一部を取り出すなら substring や take、区切って分けるなら split、桁をそろえるなら padStart や format です。

この記事は、80個以上の書き方を「やりたいこと・書き方・結果」の逆引き表にし、結果の列はすべて実際に実行して得た値を載せています。あわせて、文字列テンプレートでドル記号そのものを出す方法と複数ドル補間、substring で起きる StringIndexOutOfBoundsException、split が空文字や正規表現でどう変わるか、toUpperCase から uppercase への新旧対応もコンパイル結果つきで整理しました。

確認環境:Ubuntu 24.04 / Kotlin 2.4.20(kotlinc-jvm)/ OpenJDK 21.0.10。表の「結果」列とコードの出力は、すべてこの環境で実行したものです。

文字列操作の早見表(目的別)

目的 まず使う関数 例 結果
取り出す substring / take / substringAfter "Hello, Kotlin".substring(7, 10) "Kot"
分ける split / lines / chunked "pen,note,,bag".split(",") [pen, note, , bag]
つなぐ + / テンプレート / joinToString listOf("a", "b", "c").joinToString(", ") "a, b, c"
置き換える replace / replaceFirst / replaceFirstChar "a-b-c".replace("-", "+") "a+b+c"
整える trim / padStart / format / uppercase "7".padStart(3, '0') "007"
調べる contains / startsWith / indexOf / isBlank "Hello, Kotlin".indexOf("l") 2
変換する toInt / toIntOrNull / toString "4x".toIntOrNull() null

位置(インデックス)は 0 から数えます。substring(7, 10) は「7番目から10番目の手前まで」で、終了位置の文字は含みません。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin文字列操作一覧|split・substring・format」

この記事のコードの動かし方

コードはすべて単独のファイルで動きます。保存して次の2行で実行できます。

kotlinc sample.kt -include-runtime -d sample.jar
java -jar sample.jar

日本語の出力が ? になるときは、java -Dstdout.encoding=UTF-8 -jar sample.jar と指定します。

目的別の逆引き表(実行結果つき)

表の中の変数は次の値です。結果の列は、文字列を "..."、1文字を '...' で表示しています。

val s = "Hello, Kotlin"
val mail = "taro@example.com"
val path = "/home/taro/report.final.csv"
val csv = "pen,note,,bag"
val nothing: String? = null

取り出す

やりたいこと 書き方 結果
先頭から3文字 s.take(3) "Hel"
末尾から6文字 s.takeLast(6) "Kotlin"
開始位置〜終了位置の手前 s.substring(7, 10) "Kot"
開始位置から最後まで s.substring(7) "Kotlin"
先頭2文字を除く s.drop(2) "llo, Kotlin"
末尾2文字を除く s.dropLast(2) "Hello, Kotl"
1文字だけ s[0] 'H'
範囲外なら null s.getOrNull(99) null
区切りより前 mail.substringBefore("@") "taro"
区切りより後ろ mail.substringAfter("@") "example.com"
最後の区切りより後ろ path.substringAfterLast("/") "report.final.csv"
最後の区切りより前 path.substringBeforeLast(".") "/home/taro/report.final"
区切りが無いときの既定値 mail.substringAfter("#", "なし") "なし"
先頭の決まった文字列を外す path.removePrefix("/home/") "taro/report.final.csv"
末尾の決まった文字列を外す path.removeSuffix(".csv") "/home/taro/report.final"
前後の囲みを外す "[memo]".removeSurrounding("[", "]") "memo"
条件に合う文字だけ "a1b2c3".filter { it.isDigit() } "123"

分ける

やりたいこと 書き方 結果
区切り文字で分ける csv.split(",") [pen, note, , bag]
空の要素を除く csv.split(",").filter { it.isNotEmpty() } [pen, note, bag]
分ける数を制限 csv.split(",", limit = 2) [pen, note,,bag]
複数の区切り文字 "a,b;c".split(",", ";") [a, b, c]
連続した空白で分ける "a b c".split(Regex("\\s+")) [a, b, c]
行ごとに分ける "x\ny\r\nz".lines() [x, y, z]
決まった文字数ごと "20261003".chunked(4) [2026, 1003]
1文字ずつ "abc".toList() [a, b, c]
2つに分けて受け取る val (user, domain) = mail.split("@") "taro / example.com"

つなぐ

やりたいこと 書き方 結果
+ でつなぐ "Hello" + ", " + "Kotlin" "Hello, Kotlin"
リストを区切りつきでつなぐ listOf("a", "b", "c").joinToString(", ") "a, b, c"
前後に文字を付けてつなぐ listOf(1, 2).joinToString(", ", "[", "]") "[1, 2]"
変換しながらつなぐ listOf("a", "b").joinToString("-") { it.uppercase() } "A-B"
同じ文字列を繰り返す "ab".repeat(3) "ababab"
少しずつ組み立てる buildString { append("id="); append(7) } "id=7"

置き換える

やりたいこと 書き方 結果
すべて置き換える "a-b-c".replace("-", "+") "a+b+c"
最初の1つだけ "a-b-c".replaceFirst("-", "+") "a+b-c"
大文字小文字を区別せず "Aa".replace("a", "x", ignoreCase = true) "xx"
正規表現で置き換える "a1b22".replace(Regex("\\d+"), "#") "a#b#"
一致した部分を加工 "a1b22".replace(Regex("\\d+")) { "[" + it.value + "]" } "a[1]b[22]"
位置を指定して置き換える "2026-10-03".replaceRange(5, 7, "12") "2026-12-03"
先頭の1文字だけ加工 "kotlin".replaceFirstChar { it.uppercase() } "Kotlin"
指定の文字を取り除く "1,234,567".replace(",", "") "1234567"

整える

やりたいこと 書き方 結果
前後の空白を取る " ab ".trim() "ab"
末尾側だけ " ab ".trimEnd() " ab"
指定した文字を前後から取る "--ab--".trim('-') "ab"
左を0で埋める "7".padStart(3, '0') "007"
右を空白で埋める "ab".padEnd(5) + "/" "ab /"
大文字にする "Kotlin".uppercase() "KOTLIN"
小文字にする "Kotlin".lowercase() "kotlin"
ゼロ埋めの書式 "%05d".format(42) "00042"
小数点以下2桁 "%.2f".format(3.14159) "3.14"
3桁区切り "%,d".format(1234567) "1,234,567"

調べる

やりたいこと 書き方 結果
文字数 s.length 13
空文字か "".isEmpty() true
空白だけか " ".isBlank() true
null か空か nothing.isNullOrEmpty() true
null か空白だけか nothing.isNullOrBlank() true
含むか s.contains("Kot") true
大文字小文字を区別せず含むか s.contains("kot", ignoreCase = true) true
〜で始まるか s.startsWith("Hello") true
〜で終わるか path.endsWith(".csv") true
最初に出てくる位置 s.indexOf("l") 2
最後に出てくる位置 s.lastIndexOf("l") 10
見つからないとき s.indexOf("z") -1
条件に合う文字の数 s.count { it == 'l' } 3
すべて数字か "12345".all { it.isDigit() } true
正規表現に全体が一致するか "A-102".matches(Regex("[A-Z]-\\d{3}")) true
内容が同じか "abc" == "abc" true
大文字小文字を区別せず比べる "ABC".equals("abc", ignoreCase = true) true
空なら既定値 "".ifEmpty { "なし" } "なし"
空白だけなら既定値 " ".ifBlank { "なし" } "なし"
null なら空文字 nothing.orEmpty() ""

変換する

やりたいこと 書き方 結果
整数にする "42".toInt() 42
変換できなければ null "4x".toIntOrNull() null
小数にする "3.14".toDouble() 3.14
16進数の文字列を整数に "ff".toInt(16) 255
真偽値にする "true".toBoolean() true
厳密に真偽値へ "yes".toBooleanStrictOrNull() null
数値を文字列に 42.toString() "42"
数値を2進数の文字列に 10.toString(2) "1010"
文字の配列にする "abc".toCharArray() [a, b, c]
UTF-8 のバイト列にする "あ".encodeToByteArray() [-29, -127, -126]
文字コードを得る 'A'.code 65
数字の文字を数値に '7'.digitToInt() 7
文字コードから文字へ 65.toChar() 'A'

Kotlin の文字列は変更できない(イミュータブルな)値です。公式ドキュメントにも、文字列を変えるように見える操作はすべて新しい文字列を作る、と書かれています。trim() や replace() を呼んでも元の変数は変わらないので、結果は必ず変数で受け取ります。

val original = "  kotlin  "
val trimmed = original.trim().uppercase()
println("[$original] [$trimmed]")   // 元の文字列は変わらない
[  kotlin  ] [KOTLIN]

文字列テンプレートの書き方(ドル記号と波かっこ)

文字列の中に $変数名 と書くと値が埋め込まれ、${式} と書くと式の結果が埋め込まれます。 文字列を + でつなぐより読みやすく、Kotlin ではこちらが基本の書き方です。

data class Item(val name: String, val price: Int)

fun main() {
    val name = "Kotlin"
    val count = 3
    val item = Item("pen", 120)
    val list = listOf("a", "b")

    println("Hello, $name")                       // 変数
    println("${name}Lang")                        // 直後に文字が続くとき
    println("合計 ${count * item.price} 円")        // 式
    println("名前は ${item.name}、長さ ${name.length}")  // プロパティ・メソッド
    println("$item.name")                         // {} なしだと item まで
    println("先頭は ${list[0]}、${if (count > 2) "多い" else "少ない"}")
    println("価格: \$100")                         // $ そのもの(エスケープ)
    println("価格: ${'$'}100")                     // $ そのもの(式で書く)
    println("100$ / $ 100 / 5$!")                 // 後ろが名前でなければそのまま
    println("""C:\temp\$name""")                  // raw string でも $name は展開される
    println("""価格: ${'$'}name""")                // raw string で $ を出す
    println($$"""{"$schema": "v1", "title": "$$name"}""")   // 複数ドル補間
    println($$"$name は展開されず、$$name は展開される")
}

実行結果

Hello, Kotlin
KotlinLang
合計 360 円
名前は pen、長さ 6
Item(name=pen, price=120).name
先頭は a、多い
価格: $100
価格: $100
100$ / $ 100 / 5$!
C:\temp\Kotlin
価格: $name
{"$schema": "v1", "title": "Kotlin"}
$name は展開されず、Kotlin は展開される

つまずきやすい点は3つです。

  • "$item.name" は item までしか展開されません。 5行目の出力のとおり、item 全体の文字列のあとに .name がそのまま続きます。プロパティを埋め込むときは ${item.name} と波かっこで囲みます。
  • ドル記号の後ろが名前の形でなければ、エスケープなしでそのまま出ます。 "100$" や "$ 100" は書いたとおりに表示されました。金額のように直後が数字の場合も同じ理由でそのまま出ますが、\$ と書いておくと意図がはっきりします。
  • raw string(三重引用符)の中ではバックスラッシュのエスケープが効きません。 そのため \$ は使えず、${'$'} と書きます。

複数ドル補間($$ を前に付ける書き方)

JSON スキーマのようにドル記号を多く含む文字列では、${'$'} が並んで読みにくくなります。文字列の前に $$ を付けると、「ドル記号が2つ続いたときだけ展開する」というルールに変わります。 公式ドキュメントでは複数ドル補間(multi-dollar string interpolation)として説明されており、指定した個数より少ないドル記号は普通の文字として扱われます。

上の実行結果の最後の2行がその例で、$schema と $name はそのまま出力され、$$name だけが Kotlin に置き換わっています。

raw string(三重引用符)と trimIndent・trimMargin

複数行の文字列は三重引用符で書き、trimIndent() で共通の字下げを取り除きます。

fun main() {
    val name = "taro"

    val escaped = "1行目\n2行目\tタブ \"引用\" \\ 円記号"
    println(escaped)

    val raw = """
        $name さん
          ご注文を受け付けました。
        "引用符" も \n もそのまま書けます。
    """.trimIndent()
    println(raw)

    val margin = """
        |件名: お知らせ
        |  本文は字下げを残したい
        |以上
    """.trimMargin()
    println(margin)

    val pattern = """\d{3}-\d{4}"""          // raw string ならバックスラッシュを重ねなくてよい
    println(Regex(pattern).matches("123-4567"))

    val noTrim = """
        a
    """
    println("[" + noTrim + "]")
    println(raw.lines().size)
}

実行結果

1行目
2行目 タブ "引用" \ 円記号
taro さん
  ご注文を受け付けました。
"引用符" も \n もそのまま書けます。
件名: お知らせ
  本文は字下げを残したい
以上
true
[
        a
    ]
3
書き方 特徴 向いている用途
"..."(エスケープ文字列) \n \t \" \\ \$ が使える 1行の文字列
"""..."""(raw string) 改行や引用符をそのまま書ける。エスケープは効かない 複数行の文章、正規表現、パス
.trimIndent() 全行に共通する字下げと、最初と最後の空行を取り除く コードの字下げに合わせて書きたいとき
.trimMargin() 各行の縦棒までを取り除く。字下げを残したい行に便利 行ごとに字下げを変えたいとき

trimIndent を付けないと、出力の [ と ] の間のように、ソースコード上の字下げと前後の改行がそのまま文字列に入ります。

substring の使い方と StringIndexOutOfBoundsException

substring(開始, 終了) は、範囲が文字列の長さを超えると例外で止まります。 どの書き方が止まり、どの書き方なら止まらないかを並べて実行しました。

fun attempt(label: String, block: () -> Any?) {
    try {
        println("$label => ${block()}")
    } catch (e: Exception) {
        println("$label => ${e::class.simpleName}: ${e.message}")
    }
}

fun main() {
    val s = "Kotlin"
    attempt("s.substring(2, 4)") { s.substring(2, 4) }
    attempt("s.substring(2, 10)") { s.substring(2, 10) }
    attempt("s.substring(4, 2)") { s.substring(4, 2) }
    attempt("s.substring(10)") { s.substring(10) }
    attempt("s.substring(6)") { "[" + s.substring(6) + "]" }
    attempt("s[10]") { s[10] }
    attempt("\"\".first()") { "".first() }
    attempt("\"12a\".toInt()") { "12a".toInt() }
    println("--- 例外にならない書き方")
    attempt("s.take(10)") { s.take(10) }
    attempt("s.drop(10)") { "[" + s.drop(10) + "]" }
    attempt("s.getOrNull(10)") { s.getOrNull(10) }
    attempt("\"\".firstOrNull()") { "".firstOrNull() }
    attempt("\"12a\".toIntOrNull()") { "12a".toIntOrNull() }
    attempt("s.substring(2, minOf(10, s.length))") { s.substring(2, minOf(10, s.length)) }
    attempt("s.substringAfter(\"#\")") { s.substringAfter("#") }
}

実行結果

s.substring(2, 4) => tl
s.substring(2, 10) => StringIndexOutOfBoundsException: Range [2, 10) out of bounds for length 6
s.substring(4, 2) => StringIndexOutOfBoundsException: Range [4, 2) out of bounds for length 6
s.substring(10) => StringIndexOutOfBoundsException: Range [10, 6) out of bounds for length 6
s.substring(6) => []
s[10] => StringIndexOutOfBoundsException: Index 10 out of bounds for length 6
"".first() => NoSuchElementException: Char sequence is empty.
"12a".toInt() => NumberFormatException: For input string: "12a"
--- 例外にならない書き方
s.take(10) => Kotlin
s.drop(10) => []
s.getOrNull(10) => null
"".firstOrNull() => null
"12a".toIntOrNull() => null
s.substring(2, minOf(10, s.length)) => tlin
s.substringAfter("#") => Kotlin

try で囲まずに実行すると、次のように表示されて止まります(先頭と末尾の行を抜粋)。

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Range [2, 10) out of bounds for length 6
    at java.base/java.lang.String.substring(String.java:2823)
    at Ex2Kt.main(ex2.kt:3)

メッセージの Range [2, 10) out of bounds for length 6 は、「2 以上 10 未満の範囲を求められたが、長さは 6」という意味です。

止まる書き方 止まらない書き方 範囲外のときの結果
s.substring(0, 10) s.take(10) あるだけ返す
s.substring(10) s.drop(10) 空文字
s[10] s.getOrNull(10) null
"".first() "".firstOrNull() null
"12a".toInt() "12a".toIntOrNull() null
s.substring(s.indexOf("#") + 1) s.substringAfter("#") 区切りが無ければ元の文字列(第2引数で変更可)

長さが決まっていない入力には take / drop / substringBefore / substringAfter を使うと、長さの確認を自分で書かずに済みます。s.substring(6) のように開始位置がちょうど長さと同じ場合は、例外にならず空文字が返ります。

split の結果の違い:空文字・連続した区切り・正規表現

split は「区切りの数 + 1 個」の要素を返すのが基本で、区切りが連続していれば空文字の要素ができます。入力と書き方を変えて要素数を出しました。

fun show(label: String, list: List<String>) {
    println(label.padEnd(40) + " => 要素数 ${list.size} " + list.map { "\"$it\"" })
}

fun main() {
    show("\"\".split(\",\")", "".split(","))
    show("\"a,b\".split(\",\")", "a,b".split(","))
    show("\"a,,b\".split(\",\")", "a,,b".split(","))
    show("\",a,b,\".split(\",\")", ",a,b,".split(","))
    show("\"a,b\".split(\"\")", "a,b".split(""))
    show("\"a b  c\".split(\" \")", "a b  c".split(" "))
    show("\"a b  c\".split(Regex(\"\\\\s+\"))", "a b  c".split(Regex("\\s+")))
    show("\" a b \".split(Regex(\"\\\\s+\"))", " a b ".split(Regex("\\s+")))
    show("\" a b \".trim().split(Regex(\"\\\\s+\"))", " a b ".trim().split(Regex("\\s+")))
    show("\"a.b.c\".split(\".\")", "a.b.c".split("."))
    show("\"a.b.c\".split(Regex(\".\"))", "a.b.c".split(Regex(".")))
    show("\"a.b.c\".split(Regex(\"\\\\.\"))", "a.b.c".split(Regex("\\.")))
    show("\"a1b22c\".split(Regex(\"\\\\d+\"))", "a1b22c".split(Regex("\\d+")))
    show("\"a, b ,c\".split(\",\").map { it.trim() }", "a, b ,c".split(",").map { it.trim() })
    show("\"a,b,c\".split(\",\", limit = 2)", "a,b,c".split(",", limit = 2))
    show("\"A-b-C\".split(\"B\", ignoreCase = true)", "A-b-C".split("B", ignoreCase = true))
    show("\"a,,b\".split(\",\").filter { it.isNotEmpty() }", "a,,b".split(",").filter { it.isNotEmpty() })
    show("\"\".split(\",\").filter { it.isNotEmpty() }", "".split(",").filter { it.isNotEmpty() })
    show("\"a,b,,\".split(\",\")", "a,b,,".split(","))
}

実行結果

"".split(",")                            => 要素数 1 [""]
"a,b".split(",")                         => 要素数 2 ["a", "b"]
"a,,b".split(",")                        => 要素数 3 ["a", "", "b"]
",a,b,".split(",")                       => 要素数 4 ["", "a", "b", ""]
"a,b".split("")                          => 要素数 5 ["", "a", ",", "b", ""]
"a b  c".split(" ")                      => 要素数 4 ["a", "b", "", "c"]
"a b  c".split(Regex("\\s+"))            => 要素数 3 ["a", "b", "c"]
" a b ".split(Regex("\\s+"))             => 要素数 4 ["", "a", "b", ""]
" a b ".trim().split(Regex("\\s+"))      => 要素数 2 ["a", "b"]
"a.b.c".split(".")                       => 要素数 3 ["a", "b", "c"]
"a.b.c".split(Regex("."))                => 要素数 6 ["", "", "", "", "", ""]
"a.b.c".split(Regex("\\."))              => 要素数 3 ["a", "b", "c"]
"a1b22c".split(Regex("\\d+"))            => 要素数 3 ["a", "b", "c"]
"a, b ,c".split(",").map { it.trim() }   => 要素数 3 ["a", "b", "c"]
"a,b,c".split(",", limit = 2)            => 要素数 2 ["a", "b,c"]
"A-b-C".split("B", ignoreCase = true)    => 要素数 2 ["A-", "-C"]
"a,,b".split(",").filter { it.isNotEmpty() } => 要素数 2 ["a", "b"]
"".split(",").filter { it.isNotEmpty() } => 要素数 0 []
"a,b,,".split(",")                       => 要素数 4 ["a", "b", "", ""]
押さえておきたい結果 内容 対応
空文字を split すると要素数は 1 [""] が返る(空のリストではない) filter { it.isNotEmpty() } を付ける、または先に isEmpty() で分ける
区切りが連続すると空文字の要素ができる "a,,b" は3要素 空欄として扱うならそのまま、不要なら filter
先頭・末尾の区切りでも空文字ができる ",a,b," は4要素、"a,b,," も4要素 末尾の空要素も残る点に注意
空白1つで分けると連続空白で空要素 "a b c".split(" ") は4要素 Regex("\\s+") で分ける
前後に空白があると正規表現でも空要素 " a b " は4要素 先に trim() する
文字列の "." はそのままの文字 "a.b.c".split(".") は3要素 そのまま使える
Regex(".") は任意の1文字 すべての文字が区切りになり空文字が6個 Regex("\\.") とエスケープする
区切りの前後の空白 "a, b ,c" map { it.trim() } を続ける

文字列を渡す split は、渡した文字列をそのままの文字として扱います。 正規表現として区切りたいときだけ Regex(...) か "...".toRegex() を渡します。同じ "a,b,," を Java の String.split で分けると [a, b] の2要素になり(末尾の空要素が落ちる)、Kotlin の4要素とは結果がちがうことも、同じ環境で確認しました。

format で桁や幅をそろえる

数値の桁数、小数点以下の桁、幅をそろえたいときは format を使います。 書式の文字列に対して .format(値...) と書きます。公式ドキュメントでは、JVM 上で使える書式指定として紹介されています。

import java.util.Locale

fun main() {
    val name = "pen"; val qty = 3; val price = 1234.5
    println("%s x %d = %.1f".format(name, qty, qty * price))
    println(String.format("%s x %d", name, qty))
    println("%-6s|%6s|".format("ab", "cd"))
    println("%05d / %+d / %x / %X / %o".format(42, 42, 255, 255, 8))
    println("%,d / %,.2f".format(1234567, 1234567.891))
    println("%.0f / %.3f / %10.2f|".format(2.5, 2.0 / 3, 3.14159))
    println("%b / %c / %%".format(true, 'K'))
    println("%2\$s-%1\$s".format("a", "b"))
    println("%,.2f".format(Locale.US, 1234567.891))
    println("%,.2f".format(Locale.GERMANY, 1234567.891))
    println("既定のロケール: " + Locale.getDefault())
    try { println("%d".format("abc")) } catch (e: Exception) { println(e::class.simpleName + ": " + e.message) }
    try { println("%s %s".format("a")) } catch (e: Exception) { println(e::class.simpleName + ": " + e.message) }
    println("3".padStart(3, '0') + " / " + 3.toString().padStart(3) + " / " + "%3d".format(3))
    println(1234.5.toString() + " / " + 1e10.toString() + " / " + (0.1 + 0.2))
    println("%.2f".format(0.125) + " / " + "%.2f".format(0.135) + " / " + "%.1f".format(0.25))
}

実行結果

pen x 3 = 3703.5
pen x 3
ab    |    cd|
00042 / +42 / ff / FF / 10
1,234,567 / 1,234,567.89
3 / 0.667 /       3.14|
true / K / %
b-a
1,234,567.89
1.234.567,89
既定のロケール: en
IllegalFormatConversionException: d != java.lang.String
MissingFormatArgumentException: Format specifier '%s'
003 /   3 /   3
1234.5 / 1.0E10 / 0.30000000000000004
0.13 / 0.14 / 0.3
書式 意味 例 結果
%s 文字列 "%s".format("pen") pen
%d 整数 "%d".format(3) 3
%05d 5桁でゼロ埋め "%05d".format(42) 00042
%+d 符号つき "%+d".format(42) +42
%,d 3桁区切り "%,d".format(1234567) 1,234,567
%.2f 小数点以下2桁 "%.2f".format(3.14159) 3.14
%10.2f 全体の幅10、小数2桁 "%10.2f".format(3.14159) 左に空白6つと 3.14
%6s 幅6で右寄せ "%6s".format("cd") 左に空白4つと cd
%-6s 幅6で左寄せ "%-6s".format("ab") ab と右に空白4つ
%x / %X 16進数 "%x".format(255) ff
%% パーセント記号そのもの "%%".format() %
%2$s 2番目の引数 "%2\$s-%1\$s".format("a", "b") b-a

使うときのポイントです。

  • 書式と値の型が合わないと IllegalFormatConversionException、値の数が足りないと MissingFormatArgumentException になります。コンパイルでは見つからず実行時に分かるので、書式を変えたら一度動かして確かめます。
  • 3桁区切りや小数点の記号はロケールで変わります。 同じ %,.2f でも、Locale.US では 1,234,567.89、Locale.GERMANY では 1.234.567,89 になりました。表示を固定したいときは "%,.2f".format(Locale.US, 値) のようにロケールを渡します。
  • Kotlin の文字列の中で %2$s と書くときは \$ とエスケープします($s がテンプレートとして解釈されないようにするため)。
  • ゼロ埋めだけなら padStart でも書けます。 "3".padStart(3, '0') と "%03d".format(3) はどちらも 003 です。

正規表現で探す・取り出す・置き換える

決まった形の文字列を探すときは Regex を使います。 パターンは raw string で書くとバックスラッシュを重ねずに済みます。

fun main() {
    val text = "注文 A-102 と B-7 と C-0345 を受け付けました"
    val code = Regex("""([A-Z])-(\d+)""")

    println(code.containsMatchIn(text))                    // 含むか
    println(code.find(text)?.value)                        // 最初の一致
    println(code.findAll(text).map { it.value }.toList())  // すべての一致
    println(code.find(text)?.groupValues)                  // グループ(0番は全体)

    val (letter, number) = code.find(text)!!.destructured  // 分割して受け取る
    println("$letter / $number")

    println(code.replace(text) { m -> m.groupValues[1] + m.groupValues[2].padStart(4, '0') })
    println(text.replace(code, "$1$2"))                    // $1 $2 でグループを参照

    val date = Regex("""(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})""")
    val m = date.matchEntire("2026-10-03")                 // 全体が一致したときだけ
    println(m?.groups?.get("y")?.value + "年" + m?.groups?.get("m")?.value + "月")
    println(date.matchEntire("2026-10-03 です"))

    println("a.b".replace(".", "-"))                       // 文字列の replace はそのままの文字として扱う
    println("a.b".replace(Regex("."), "-"))                // Regex の . は任意の1文字
    println("Kotlin".contains(Regex("^kot", RegexOption.IGNORE_CASE)))
}

実行結果

true
A-102
[A-102, B-7, C-0345]
[A-102, A, 102]
A / 102
注文 A0102 と B0007 と C0345 を受け付けました
注文 A102 と B7 と C0345 を受け付けました
2026年10月
null
a-b
---
true

replace も split と同じく、文字列を渡せばそのままの文字、Regex を渡せば正規表現として扱われます。"a.b".replace(".", "-") は a-b、Regex(".") を渡すと全部の文字が置き換わって --- になりました。

非推奨になった関数の新旧対応表

以前の記事やサンプルで見かける toUpperCase や capitalize は、Kotlin 2.4.20 ではコンパイルエラーまたは警告になります。実際にコンパイルして出たメッセージを載せます。

fun main() {
    println("kotlin".toUpperCase())
    println("KOTLIN".toLowerCase())
    println("kotlin".capitalize())
    println("Kotlin".decapitalize())
    println('a'.toUpperCase())
    println('A'.toLowerCase())
    println('A'.toInt())
    println(65.toChar())
    println(65.5.toChar())
    println("a".toByte())
}

コンパイル結果(エラー)

dp.kt:2:22: error: 'fun String.toUpperCase(): String' is deprecated. Use uppercase() instead.
dp.kt:3:22: error: 'fun String.toLowerCase(): String' is deprecated. Use lowercase() instead.
dp.kt:6:17: error: 'fun Char.toUpperCase(): Char' is deprecated. Use uppercaseChar() instead.
dp.kt:7:17: error: 'fun Char.toLowerCase(): Char' is deprecated. Use lowercaseChar() instead.
dp.kt:10:18: error: 'fun toChar(): Char' is deprecated. Direct conversion to Char is deprecated. Use toInt().toChar() or Char constructor instead.

エラーになる行を除いて capitalize などだけを残すと、警告つきでコンパイルが通り、実行もできました。

dw.kt:2:22: warning: 'fun String.capitalize(): String' is deprecated. Use replaceFirstChar instead.
dw.kt:3:22: warning: 'fun String.decapitalize(): String' is deprecated. Use replaceFirstChar instead.
dw.kt:4:17: warning: 'fun toInt(): Int' is deprecated. Conversion of Char to Number is deprecated. Use Char.code property instead.
以前の書き方 今の書き方 Kotlin 2.4.20 での扱い
"kotlin".toUpperCase() "kotlin".uppercase() エラー
"KOTLIN".toLowerCase() "KOTLIN".lowercase() エラー
"kotlin".capitalize() "kotlin".replaceFirstChar { it.uppercase() } 警告
"Kotlin".decapitalize() "Kotlin".replaceFirstChar { it.lowercase() } 警告
'a'.toUpperCase() 'a'.uppercaseChar()(Char を返す)/ 'a'.uppercase()(String を返す) エラー
'A'.toLowerCase() 'A'.lowercaseChar() エラー
'A'.toInt() 'A'.code 警告
65.5.toChar() 65.5.toInt().toChar() エラー

新しい書き方を実行した結果です。

fun main() {
    println("kotlin".uppercase())
    println("KOTLIN".lowercase())
    println("kotlin".replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() })
    println("kotlin".replaceFirstChar { it.uppercase() })
    println("Kotlin".replaceFirstChar { it.lowercase() })
    println('a'.uppercaseChar())
    println('a'.uppercase())
    println('A'.lowercaseChar())
    println('A'.code)
    println(65.toChar())
    println(Char(65))
    println("hello world".split(" ").joinToString(" ") { w -> w.replaceFirstChar { it.uppercase() } })
    println("a1b22".replace(Regex("\\d+")) { "[" + it.value + "]" })
}
KOTLIN
kotlin
Kotlin
Kotlin
kotlin
A
A
a
65
A
A
Hello World
a[1]b[22]

65.toChar()(Int からの変換)と "a".toByte() はこの版でもエラーにも警告にもなりませんでした。単語ごとに先頭を大文字にしたいときは、最後から2行目のように split・replaceFirstChar・joinToString を組み合わせます。

比較・並べ替え・StringBuilder・文字数の数え方

fun main() {
    val a = "Kotlin"
    val b = buildString { append("Kot"); append("lin") }
    println(a == b)                 // 内容の比較
    println(a === b)                // 同じオブジェクトか
    println(a.equals("KOTLIN", ignoreCase = true))
    println(listOf("b", "A", "c").sorted())
    println(listOf("b", "A", "c").sortedWith(String.CASE_INSENSITIVE_ORDER))
    println(listOf("10", "9", "100").sorted())
    println(listOf("10", "9", "100").sortedBy { it.toInt() })

    val sb = StringBuilder()
    for (i in 1..3) sb.append(i).append(',')
    sb.setLength(sb.length - 1)
    sb.insert(0, "[").append("]")
    println(sb.toString())

    println("あいう".length)
    println("😀".length)                                  // 絵文字は Char 2つ分
    println("😀".codePointCount(0, "😀".length))
    println("あいう".encodeToByteArray().size)             // UTF-8 のバイト数

    val nullable: String? = null
    println(nullable?.length)
    println(nullable?.uppercase() ?: "なし")
    println(nullable.isNullOrBlank())
    println("値: $nullable")
}

実行結果

true
false
true
[A, b, c]
[A, b, c]
[10, 100, 9]
[9, 10, 100]
[1,2,3]
3
2
1
9
null
なし
true
値: null
知っておきたい点 内容
== は内容の比較 Kotlin の == は文字列の中身を比べる。同じオブジェクトかどうかは ===
数字の文字列の並べ替え sorted() は辞書順なので 10, 100, 9 になる。数値順は sortedBy { it.toInt() }
ループで少しずつ足す StringBuilder か buildString { } を使う
length は Char の数 日本語は1文字1、絵文字は 2 になることがある。見た目の文字数に近い値は codePointCount
null の文字列 ?. で安全に呼ぶ。テンプレートに入れると null という文字になる

よくある質問

Q. substring と take はどう使い分けますか? A. 位置がはっきり決まっていて、長さも足りると分かっているなら substring、入力の長さが分からないなら take / drop が向いています。substring は範囲が長さを超えると StringIndexOutOfBoundsException になり、take は「あるだけ」を返します。

Q. split の結果に空文字が入るのを避けるには? A. split(",").filter { it.isNotEmpty() } と続けます。空白区切りなら trim().split(Regex("\\s+")) にすると、連続した空白や前後の空白による空要素ができません。

Q. 文字列の中にドル記号そのものを出すには? A. 通常の文字列では \$、raw string では ${'$'} と書きます。ドル記号が多い文字列は、前に $$ を付ける複数ドル補間にすると、$$name だけが展開され、1つだけのドル記号はそのまま出ます。

Q. toUpperCase が使えないと表示されました。 A. Kotlin 2.4.20 では 'fun String.toUpperCase(): String' is deprecated. Use uppercase() instead. というエラーになります。uppercase() に置き換えます。capitalize は replaceFirstChar { it.uppercase() } です。

まとめ

  • 文字列操作は「取り出す・分ける・つなぐ・置き換える・整える・調べる・変換する」で引くと探しやすい
  • 文字列は変更できない値。 trim や replace の結果は新しい文字列として受け取る
  • テンプレートは $変数 と ${式}。 ドル記号そのものは \$、raw string では ${'$'}、多いときは $$ を前に付ける
  • substring は範囲外で StringIndexOutOfBoundsException。 長さが決まらない入力には take / drop / substringAfter
  • split は空文字で要素数 1、連続した区切りで空要素。 文字列はそのままの文字、Regex は正規表現として扱われる
  • 桁や幅は format と padStart。 3桁区切りの記号はロケールで変わる
  • toUpperCase → uppercase、capitalize → replaceFirstChar、Char.toInt → code に置き換える

参考

  • Kotlin 公式ドキュメント Strings(文字列テンプレート、複数ドル補間、raw string、String.format): https://kotlinlang.org/docs/strings.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin文字列操作一覧|split・substring・format」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?