- Kotlin
Kotlin文字列操作一覧|split・substring・format
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期連続の黒字決算。