- Kotlin
Kotlin 配列の使い方 arrayOf・IntArrayとListの違い
Kotlin の配列とは、作ったあとに長さを変えられない、同じ型の値を並べた入れ物です。arrayOf(1, 2, 3) や IntArray(5) で作り、a[0] で読み書きします。 要素を増やしたり減らしたりしたいときは MutableList、読み取り専用で渡したいときは List が向いています。
この記事では、配列の作り方を一覧にまとめ、Array<Int>・IntArray・List・MutableList の違いを表で比べます。二次元配列の初期化、== と contentEquals の違い、println で中身が表示されないときの対処、範囲外アクセスの例外文は、すべて実際に動かした出力を載せています。
確認環境:Ubuntu 24.04、Kotlin 2.4.20(kotlinc-jvm)、OpenJDK 21.0.10。コードは kotlinc a.kt -include-runtime -d a.jar でコンパイルし、java -Dstdout.encoding=UTF-8 -jar a.jar で実行しています(-Dstdout.encoding=UTF-8 は日本語の出力を UTF-8 にそろえる指定です)。
Kotlin 配列の早見表
作り方の一覧
| 書き方 | 型 | 中身 | 使いどころ |
|---|---|---|---|
arrayOf(10, 20, 30) |
Array<Int> |
[10, 20, 30] |
値を並べて作る |
Array(5) { i -> i * i } |
Array<Int> |
[0, 1, 4, 9, 16] |
長さと計算式で作る |
intArrayOf(1, 2, 3) |
IntArray |
[1, 2, 3] |
数値を並べて作る(プリミティブ配列) |
IntArray(4) |
IntArray |
[0, 0, 0, 0] |
0 で埋めた数値配列 |
IntArray(4) { it + 1 } |
IntArray |
[1, 2, 3, 4] |
長さと計算式で数値配列 |
arrayOfNulls<String>(3) |
Array<String?> |
[null, null, null] |
中身をあとから入れる |
emptyArray<String>() |
Array<String> |
[] |
長さ 0 の配列 |
Array(3) { "" } |
Array<String> |
空文字 3 つ | null を使わずに埋める |
list.toTypedArray() |
Array<T> |
List と同じ | List から配列へ |
list.toIntArray() |
IntArray |
List と同じ | List<Int> から数値配列へ |
Array(3) { IntArray(4) } |
Array<IntArray> |
3 行 4 列の 0 | 二次元配列 |
よく使う操作の一覧
| やりたいこと | 書き方 | 結果の型 |
|---|---|---|
| 読む・書く | a[0]、a[0] = 5 |
要素の型 |
| 要素数 | a.size |
Int |
| 添字の範囲・最後の添字 | a.indices、a.lastIndex |
IntRange、Int |
| 範囲外でも止まらずに読む | a.getOrNull(i)、a.getOrElse(i) { 0 } |
要素の型(null 許容) |
| 中身を表示する | a.contentToString()、a.joinToString() |
String |
| 二次元の中身を表示する | a.contentDeepToString() |
String |
| 中身を比べる | a contentEquals b |
Boolean |
| 二次元の中身を比べる | a contentDeepEquals b |
Boolean |
| 並べ替え(元を変える) | a.sort() |
なし |
| 並べ替え(新しく作る) | a.sortedArray()、a.sorted() |
配列、List |
| 複製 | a.copyOf()、a.copyOfRange(1, 3) |
配列 |
| 要素を足した新しい配列 | a + 4 |
配列 |
| List に変える | a.toList()、a.toMutableList() |
List、MutableList |
| 可変長引数に渡す | f(*a) |
― |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Kotlin 配列の使い方 arrayOf・IntArrayとListの違い」
配列を作る:arrayOf・Array(n){ }・IntArray・arrayOfNulls・emptyArray
早見表の作り方をそのまま 1 本のコードにしました。contentToString() は配列の中身を文字列にする関数です(理由は後半で説明します)。
fun main() {
val a = arrayOf(10, 20, 30) // Array<Int>
val b = Array(5) { i -> i * i } // サイズと初期化ラムダ
val c = intArrayOf(1, 2, 3) // IntArray(プリミティブ配列)
val d = IntArray(4) // 0 で埋まる
val e = IntArray(4) { it + 1 } // ラムダで初期化
val f = arrayOfNulls<String>(3) // Array<String?>、null で埋まる
val g = emptyArray<String>() // 長さ 0
val h = Array(3) { "" } // 空文字で埋める
val i = "a,b,c".split(",").toTypedArray() // List から配列へ
val j = BooleanArray(2) // false で埋まる
val k = DoubleArray(2) // 0.0 で埋まる
val l = CharArray(2) { 'x' }
println(a.contentToString())
println(b.contentToString())
println(c.contentToString())
println(d.contentToString())
println(e.contentToString())
println(f.contentToString())
println("${g.contentToString()} size=${g.size}")
println(h.contentToString())
println(i.contentToString())
println(j.contentToString())
println(k.contentToString())
println(l.contentToString())
}
実行結果:
[10, 20, 30]
[0, 1, 4, 9, 16]
[1, 2, 3]
[0, 0, 0, 0]
[1, 2, 3, 4]
[null, null, null]
[] size=0
[, , ]
[a, b, c]
[false, false]
[0.0, 0.0]
[x, x]
押さえておきたい点は 3 つです。
Array(n) { ... }のラムダには添字(0 から n-1)が渡されます。 引数が 1 つなのでitでも書けます。IntArray(n)はラムダなしで作れ、0 で埋まります。Array<Int>(n)はラムダが必須です(後述のエラー文を参照)。arrayOfNulls<String>(3)の型はArray<String?>です。 取り出した値は null 許容なので、?.や?:で扱います。null を使いたくないときはArray(3) { "" }のように初期値で埋めます。
作るときに出やすいコンパイルエラー
次のコードをコンパイルすると、5 か所でエラーになります。
fun main() {
val a: IntArray = arrayOf(1, 2, 3)
val b: Array<Any> = arrayOf("a", "b") as Array<String>
val c = intArrayOf(1, 2, 3)
c.add(4)
val d: Array<String> = arrayOfNulls<String>(3)
val e = arrayOfNulls(3)
}
err1.kt:2:21: error: initializer type mismatch: expected 'IntArray', actual 'Array<Int>'.
val a: IntArray = arrayOf(1, 2, 3)
^
err1.kt:3:23: error: initializer type mismatch: expected 'Array<Any>', actual 'Array<String>'.
val b: Array<Any> = arrayOf("a", "b") as Array<String>
^
err1.kt:5:7: error: unresolved reference 'add' on receiver of type 'IntArray'.
c.add(4)
^^^
err1.kt:6:26: error: initializer type mismatch: expected 'Array<String>', actual 'Array<String?>'.
val d: Array<String> = arrayOfNulls<String>(3)
^
err1.kt:7:13: error: cannot infer type for type parameter 'T'. Specify it explicitly.
val e = arrayOfNulls(3)
^^^^^^^^^^^^
| エラー文の要点 | 理由 | 直し方 |
|---|---|---|
expected 'IntArray', actual 'Array<Int>' |
arrayOf が返すのは Array<Int> で、IntArray とは別の型 |
intArrayOf(1, 2, 3) にする |
expected 'Array<Any>', actual 'Array<String>' |
配列は型引数が違うと代入できない | Array<out Any> で受けるか、List<Any> を使う |
| unresolved reference 'add' | 配列に add は無い(長さ固定) |
MutableList を使うか、c + 4 で新しい配列を作る |
expected 'Array<String>', actual 'Array<String?>' |
arrayOfNulls の要素は null 許容 |
型を Array<String?> にするか Array(3) { "" } で作る |
| cannot infer type for type parameter 'T' | 要素の型を推論する材料が無い | arrayOfNulls<String>(3) と型を書く |
Array<Int>(3) のようにラムダを省いた場合は、次のエラーになります。
err2.kt:6:13: error: no value passed for parameter 'init'.
val x = Array<Int>(3)
^^^^^
読み書きとループの基本
fun main() {
val scores = intArrayOf(70, 85, 90)
println(scores[0]) // 読み取り(get)
scores[1] = 88 // 書き換え(set)
println(scores.size) // 要素数
println(scores.indices) // 添字の範囲
println(scores.lastIndex) // 最後の添字
println(scores.first())
println(scores.last())
for (s in scores) print("$s ")
println()
for (i in scores.indices) print("$i:${scores[i]} ")
println()
for ((i, s) in scores.withIndex()) print("$i=$s ")
println()
scores.forEachIndexed { i, s -> print("[$i]$s ") }
println()
println(scores.getOrNull(5)) // 範囲外は null
println(scores.getOrElse(5) { -1 }) // 範囲外は既定値
println(85 in scores) // 含むか
println(scores.indexOf(90))
}
実行結果:
70
3
0..2
2
70
90
70 88 90
0:70 1:88 2:90
0=70 1=88 2=90
[0]70 [1]88 [2]90
null
-1
false
2
val で宣言していても scores[1] = 88 は通ります。val が固定するのは「変数がどの配列を指すか」で、配列の中身は書き換えられます。 85 in scores が false なのは、直前に 85 を 88 に書き換えたためです。
ループは目的で選びます。
| 欲しいもの | 書き方 |
|---|---|
| 値だけ | for (s in scores) |
| 添字だけ | for (i in scores.indices) |
| 添字と値 | for ((i, s) in scores.withIndex())、forEachIndexed |
Array・IntArray・List・MutableList の違い
比較表
| 観点 | Array<Int> |
IntArray |
List<Int> |
MutableList<Int> |
|---|---|---|---|---|
| 作り方 | arrayOf(1, 2, 3) |
intArrayOf(1, 2, 3) |
listOf(1, 2, 3) |
mutableListOf(1, 2, 3) |
| JVM 上の型 | Integer[] |
int[] |
java.util.List |
java.util.List |
| 長さの変更 | できない | できない | できない | できる(add・removeAt) |
| 要素の書き換え | できる | できる | できない | できる |
| 読み取り専用で渡す | 専用の型は無い | 専用の型は無い | この型自体が読み取り専用 | List 型として渡せる |
println の表示 |
型名@ハッシュ値 | 型名@ハッシュ値 | [1, 2, 3] |
[1, 2, 3] |
== の意味 |
同じ配列かどうか | 同じ配列かどうか | 中身が同じか | 中身が同じか |
| 中身の比較 | contentEquals |
contentEquals |
== |
== |
| null を入れる | Array<Int?> なら可 |
不可 | List<Int?> なら可 |
MutableList<Int?> なら可 |
| 値の持ち方 | 箱に入れた Integer |
int のまま |
箱に入れた Integer |
箱に入れた Integer |
| 別の要素型への代入 | 型引数が同じ場合のみ | ― | List<String> は List<Any> に渡せる |
型引数が同じ場合のみ |
実際に確かめたコードと出力です。
fun main() {
val boxed: Array<Int> = arrayOf(1, 2, 3)
val prim: IntArray = intArrayOf(1, 2, 3)
val list: List<Int> = listOf(1, 2, 3)
val mlist: MutableList<Int> = mutableListOf(1, 2, 3)
println(boxed.javaClass.simpleName) // JVM 上の型
println(prim.javaClass.simpleName)
println(list.javaClass.name)
println(mlist.javaClass.name)
// 配列は要素を書き換えられるが、長さは変えられない
boxed[0] = 100
prim[0] = 100
println(boxed.contentToString())
println(prim.contentToString())
// + は元の配列を変えず、新しい配列を返す
val added = prim + 4
println("${prim.size} -> ${added.size} 同じ配列か=${prim === added}")
// MutableList は長さを変えられる
mlist.add(4)
mlist.removeAt(0)
println(mlist)
// List は println と == がそのまま使える
println(list)
println(list == listOf(1, 2, 3))
}
Integer[]
int[]
java.util.Arrays$ArrayList
java.util.ArrayList
[100, 2, 3]
[100, 2, 3]
3 -> 4 同じ配列か=false
[2, 3, 4]
[1, 2, 3]
true
Array と IntArray は別の型
Array<Int> は JVM 上で Integer[]、IntArray は int[] になります。 IntArray は数値をそのまま並べるので、数値を大量に扱う処理に向いています。同じ仕組みの型が 8 種類あります。
| Kotlin の型 | 作る関数 | Java の型 | 既定値 |
|---|---|---|---|
IntArray |
intArrayOf() |
int[] |
0 |
LongArray |
longArrayOf() |
long[] |
0 |
DoubleArray |
doubleArrayOf() |
double[] |
0.0 |
FloatArray |
floatArrayOf() |
float[] |
0.0 |
BooleanArray |
booleanArrayOf() |
boolean[] |
false |
CharArray |
charArrayOf() |
char[] |
文字コード 0 の文字 |
ByteArray |
byteArrayOf() |
byte[] |
0 |
ShortArray |
shortArrayOf() |
short[] |
0 |
String にはプリミティブ配列が無いので、文字列の配列は Array<String> を使います。
配列と List は型引数の扱いが違う
Array<String> は Array<Any> に代入できませんが、List<String> は List<Any> に代入できます。
fun main() {
val strs: Array<String> = arrayOf("a", "b")
val anys: Array<Any> = strs
val list: List<Any> = listOf("a", "b")
println(list)
}
err2.kt:3:26: error: initializer type mismatch: expected 'Array<Any>', actual 'Array<String>'.
val anys: Array<Any> = strs
^
配列は書き込みができるため、Array<Any> として受け取った側が数値を入れられてしまうと型が崩れます。Kotlin はこれをコンパイル時に止めています。読むだけの引数なら Array<out Any> と書けば受け取れます。
どれを選ぶか
Kotlin 公式ドキュメントの配列のページは、Java の API との相互運用や低レベルの要件があるときに配列を使い、ほとんどの用途ではコレクションを選ぶことを勧めています。これを踏まえた判断表です。
| 場面 | 選ぶ型 |
|---|---|
| 関数の引数・戻り値で値の並びを渡す | List |
| 要素を追加・削除しながら組み立てる | MutableList(渡すときは List 型で) |
| 数値を大量に持ち、添字で読み書きする | IntArray・DoubleArray など |
| 行と列が決まった表(盤面・行列) | Array(行) { IntArray(列) } |
Java のメソッドが String[] や int[] を求める |
Array<String>、IntArray |
vararg の引数にまとめて渡す |
配列+スプレッド演算子 * |
main の引数 |
Array<String>(fun main(args: Array<String>)) |
配列と List を相互に変換する
fun main() {
val boxed: Array<Int> = arrayOf(3, 1, 2)
val prim: IntArray = boxed.toIntArray() // Array<Int> -> IntArray
val back: Array<Int> = prim.toTypedArray() // IntArray -> Array<Int>
val list: List<Int> = prim.toList() // 配列 -> List(コピー)
val mlist: MutableList<Int> = prim.toMutableList()
val fromList: IntArray = listOf(7, 8, 9).toIntArray()
val strArr: Array<String> = listOf("a", "b").toTypedArray()
val set: Set<Int> = intArrayOf(1, 1, 2).toSet()
println(prim.contentToString())
println(back.contentToString())
println(list)
println(mlist)
println(fromList.contentToString())
println(strArr.contentToString())
println(set)
// toList はコピー、asList は元の配列を見るビュー
val src = arrayOf(1, 2, 3)
val copied = src.toList()
val view = src.asList()
src[0] = 99
println("toList=$copied asList=$view")
}
[3, 1, 2]
[3, 1, 2]
[3, 1, 2]
[3, 1, 2]
[7, 8, 9]
[a, b]
[1, 2]
toList=[1, 2, 3] asList=[99, 2, 3]
| 変換 | 関数 |
|---|---|
Array<Int> → IntArray |
toIntArray() |
IntArray → Array<Int> |
toTypedArray() |
配列 → List / MutableList / Set |
toList() / toMutableList() / toSet() |
List<Int> → IntArray |
toIntArray() |
List<T> → Array<T> |
toTypedArray() |
| 配列を List として見る(コピーしない) | asList() |
toList() は複製、asList() は元の配列を映すビューです。 出力の最後の行のとおり、元の配列を書き換えると asList() の側だけ値が変わります。
二次元配列を初期化する
Kotlin に二次元配列の専用の型は無く、「配列の配列」 で表します。3 行 4 列なら、外側に長さ 3 の配列を作り、その各要素に長さ 4 の配列を入れます。
fun main() {
// 3 行 4 列、0 で初期化
val grid = Array(3) { IntArray(4) }
grid[1][2] = 7
println(grid.contentDeepToString())
// 行と列の番号から値を作る
val table = Array(3) { r -> IntArray(4) { c -> r * 10 + c } }
for (row in table) println(row.joinToString(" "))
// 値を並べて作る
val fixed = arrayOf(
intArrayOf(1, 2, 3),
intArrayOf(4, 5, 6),
)
println("行数=${fixed.size} 列数=${fixed[0].size}")
// 文字列の二次元配列
val names = Array(2) { Array(3) { "-" } }
names[0][1] = "A"
println(names.contentDeepToString())
// 行ごとに長さが違う配列(ジャグ配列)
val jag = Array(3) { r -> IntArray(r + 1) { it } }
println(jag.contentDeepToString())
// 行と列を入れ替えて走査
for (c in 0 until 3) {
println(fixed.map { it[c] })
}
}
[[0, 0, 0, 0], [0, 0, 7, 0], [0, 0, 0, 0]]
0 1 2 3
10 11 12 13
20 21 22 23
行数=2 列数=3
[[-, A, -], [-, -, -]]
[[0], [0, 1], [0, 1, 2]]
[1, 4]
[2, 5]
[3, 6]
| 作りたいもの | 書き方 | 型 |
|---|---|---|
| 数値 3 行 4 列(0 で初期化) | Array(3) { IntArray(4) } |
Array<IntArray> |
| 数値 3 行 4 列(計算式で初期化) | Array(3) { r -> IntArray(4) { c -> r * 10 + c } } |
Array<IntArray> |
| 文字列 2 行 3 列 | Array(2) { Array(3) { "" } } |
Array<Array<String>> |
| 中身があとで決まる 2 行 3 列 | Array(2) { arrayOfNulls<String>(3) } |
Array<Array<String?>> |
| 値を並べる | arrayOf(intArrayOf(1, 2), intArrayOf(3, 4)) |
Array<IntArray> |
| 三次元 | Array(2) { Array(3) { IntArray(4) } } |
Array<Array<IntArray>> |
読み書きは grid[行][列] です。行数は grid.size、列数は grid[0].size で取れます。
同じ行を共有してしまう書き方
二次元配列で最もつまずきやすいのが、1 つの行を全部の行で使い回してしまう書き方です。
fun main() {
// 同じ行を使い回す書き方
val row = IntArray(4)
val shared = Array(3) { row }
shared[0][0] = 9
println(shared.contentDeepToString())
println(shared[0] === shared[1])
// 行ごとに新しい配列を作る書き方
val separate = Array(3) { IntArray(4) }
separate[0][0] = 9
println(separate.contentDeepToString())
println(separate[0] === separate[1])
// copyOf は外側だけの複製(行は共有される)
val shallow = separate.copyOf()
shallow[1][1] = 5
println(separate.contentDeepToString())
// 行も複製する
val deep = Array(separate.size) { separate[it].copyOf() }
deep[2][2] = 8
println(separate.contentDeepToString())
println(deep.contentDeepToString())
}
[[9, 0, 0, 0], [9, 0, 0, 0], [9, 0, 0, 0]]
true
[[9, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
false
[[9, 0, 0, 0], [0, 5, 0, 0], [0, 0, 0, 0]]
[[9, 0, 0, 0], [0, 5, 0, 0], [0, 0, 0, 0]]
[[9, 0, 0, 0], [0, 5, 0, 0], [0, 0, 8, 0]]
1 行目の出力では、shared[0][0] だけを書き換えたのに 3 行すべての先頭が 9 になっています。Array(3) { row } のラムダが毎回同じ row を返すので、3 つの行が同じ配列を指しているためです(=== の結果が true)。
| 書き方 | ラムダが返すもの | 結果 |
|---|---|---|
Array(3) { row } |
外で作った 1 つの配列 | 3 行が同じ配列を共有する |
Array(3) { IntArray(4) } |
呼ばれるたびに新しい配列 | 3 行が独立する |
ラムダの中で IntArray(4) を書くと、行ごとに新しい配列が作られます。 これが二次元配列の初期化の基本形です。
複製でも同じことが起きます。copyOf() は外側の配列だけを複製するので、5 行目の出力のとおり、複製した側の [1][1] を書き換えると元の配列も変わります。行まで独立させたいときは Array(src.size) { src[it].copyOf() } と書きます。最後の 2 行の出力で、deep への書き込みが元の配列に届いていないことが確認できます。
配列の比較:== と contentEquals・contentDeepEquals
fun main() {
val a = intArrayOf(1, 2, 3)
val b = intArrayOf(1, 2, 3)
println(a == b) // 参照の比較
println(a.equals(b))
println(a contentEquals b) // 中身の比較
println(a.toList() == b.toList())
val m1 = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
val m2 = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
println(m1 contentEquals m2) // 行は参照で比べられる
println(m1 contentDeepEquals m2) // 入れ子の中身まで比べる
println(a.hashCode() == b.hashCode())
println(a.contentHashCode() == b.contentHashCode())
// data class に配列を持たせた場合
data class Row(val id: Int, val cells: IntArray)
println(Row(1, intArrayOf(1, 2)) == Row(1, intArrayOf(1, 2)))
data class RowL(val id: Int, val cells: List<Int>)
println(RowL(1, listOf(1, 2)) == RowL(1, listOf(1, 2)))
// null を含めた比較
val n: IntArray? = null
println(n contentEquals null)
}
コンパイル時に、次の警告が出ます。
equal.kt:5:15: warning: '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use '===' to remove the warning.
println(a == b) // 参照の比較
^^
実行結果:
false
false
true
true
false
true
false
true
false
true
true
| 比べ方 | 一次元(中身が同じ) | 二次元(中身が同じ) | 比べているもの |
|---|---|---|---|
a == b |
false | false | 同じ配列かどうか |
a contentEquals b |
true | false | 要素を 1 段だけ比べる |
a contentDeepEquals b |
true | true | 入れ子の配列の中身まで比べる |
a.toList() == b.toList() |
true | false(行が配列のため) | List としての中身 |
- 配列の
==は「同じ配列を指しているか」を調べます。 中身が同じでも、別々に作った配列ならfalseです。Kotlin 2.4.20 ではコンパイル時に警告で知らせてくれます。 - 中身は
contentEqualsで比べます。infix関数なのでa contentEquals bと書けます。 - 二次元配列は
contentDeepEqualsを使います。contentEqualsは行どうしを参照で比べるため、5 行目の出力のとおりfalseになります。 - ハッシュ値も同様で、中身から計算したいときは
contentHashCode()(入れ子はcontentDeepHashCode())を使います。
data class に配列を持たせるとき
data class が自動で作る equals は、プロパティを == で比べます。配列のプロパティは参照の比較になるので、出力の 9 行目のとおり、中身が同じ Row どうしでも false です。値として比べたいデータには List を持たせると、10 行目のように true になります。配列のまま持つ場合は、equals と hashCode を contentEquals・contentHashCode で書き直します。
println で中身が出ないとき:contentToString
fun main() {
val prim = intArrayOf(1, 2, 3)
val strs = arrayOf("a", "b")
val grid = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
println(prim)
println(strs)
println("値は $prim")
println(prim.contentToString())
println(strs.contentToString())
println(prim.joinToString())
println(prim.joinToString(separator = "-", prefix = "<", postfix = ">"))
println(prim.toList())
println(grid.contentToString()) // 行が型名@ハッシュのまま
println(grid.contentDeepToString()) // 入れ子まで展開
}
[I@3d4eac69
[Ljava.lang.String;@42a57993
値は [I@3d4eac69
[1, 2, 3]
[a, b]
1, 2, 3
<1-2-3>
[1, 2, 3]
[[I@6e0be858, [I@61bbe9ba]
[[1, 2], [3, 4]]
[I@3d4eac69 は「int の配列([I)+ハッシュ値」という JVM の表記で、@ の後ろは実行のたびに変わります。配列の toString() は中身を展開しないため、println にそのまま渡したときも、文字列テンプレート "$prim" に埋め込んだときも、この表記になります。
| 表示したい形 | 書き方 | 出力 |
|---|---|---|
[1, 2, 3] |
a.contentToString() |
[1, 2, 3] |
| 区切りを自分で決める | a.joinToString("-") |
1-2-3 |
| 前後に文字を付ける | a.joinToString("-", "<", ">") |
<1-2-3> |
| 二次元 | a.contentDeepToString() |
[[1, 2], [3, 4]] |
| 二次元を 1 行ずつ | for (row in a) println(row.joinToString(" ")) |
行ごとに出力 |
二次元配列に contentToString() を使うと、9 行目の出力のように内側が型名@ハッシュ値のまま残ります。入れ子の配列には contentDeepToString() を使います。
範囲外アクセスの例外と、止まらない書き方
fun main() {
val a = intArrayOf(10, 20, 30)
try { println(a[3]) } catch (e: Exception) { println(e) }
try { println(a[-1]) } catch (e: Exception) { println(e) }
try { a[5] = 1 } catch (e: Exception) { println(e) }
try { println(IntArray(-1).size) } catch (e: Exception) { println(e) }
try { println(emptyArray<String>().first()) } catch (e: Exception) { println(e) }
try { println(listOf(10, 20, 30)[3]) } catch (e: Exception) { println(e) }
println(a.getOrNull(3))
println(a.getOrElse(3) { 0 })
println(emptyArray<String>().firstOrNull())
println(a[3])
}
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
java.lang.NegativeArraySizeException: -1
java.util.NoSuchElementException: Array is empty.
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
null
0
null
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at OobKt.main(oob.kt:13)
at OobKt.main(oob.kt)
| 操作 | 例外 | 止まらない書き方 |
|---|---|---|
a[3](長さ 3 の配列) |
ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 |
a.getOrNull(3)、a.getOrElse(3) { 0 } |
a[-1] |
ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3 |
a.last()、a[a.lastIndex] |
a[5] = 1 |
ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3 |
if (i in a.indices) a[i] = 1 |
IntArray(-1) |
NegativeArraySizeException: -1 |
長さを maxOf(n, 0) などで整える |
空の配列に first() |
NoSuchElementException: Array is empty. |
firstOrNull() |
メッセージの「Index 3 out of bounds for length 3」は、「長さ 3 の配列に添字 3 でアクセスした」 という意味です。長さ 3 の配列で使える添字は 0〜2 なので、最後の要素は a[a.size - 1]、つまり a[a.lastIndex] です。ループの範囲は 0..a.size ではなく a.indices(または 0 until a.size)にすると、添字が 1 つはみ出ることを避けられます。
捕まえなかった場合は、出力の最後のように Exception in thread "main" に続いてファイル名と行番号(oob.kt:13)が表示され、プログラムが止まります。この環境では、listOf(10, 20, 30)[3] も配列と同じ ArrayIndexOutOfBoundsException になりました(listOf が返す List の中身が配列のためです)。
並べ替え・集計・複製・連結
fun main() {
val a = intArrayOf(5, 3, 8, 1)
println(a.sorted()) // List を返す(元は変わらない)
println(a.sortedArray().contentToString()) // 配列を返す
println(a.sortedDescending())
a.sort() // 元の配列を並べ替える
println(a.contentToString())
a.reverse()
println(a.contentToString())
println("${a.sum()} ${a.max()} ${a.min()} ${a.average()}")
println(a.filter { it > 2 }) // List を返す
println(a.map { it * 2 })
println(a.copyOf(6).contentToString()) // 伸ばすと 0 で埋まる
println(a.copyOfRange(1, 3).contentToString())
println(a.sliceArray(0..1).contentToString())
a.fill(0)
println(a.contentToString())
val words = arrayOf("b", "a") + arrayOf("c")
println(words.contentToString())
println(words.sortedArray().contentToString())
// 可変長引数に配列を渡す(スプレッド演算子)
fun total(vararg xs: Int) = xs.sum()
println(total(*intArrayOf(1, 2, 3)))
val copy = arrayOf(*words, "d")
println(copy.contentToString())
}
[1, 3, 5, 8]
[1, 3, 5, 8]
[8, 5, 3, 1]
[1, 3, 5, 8]
[8, 5, 3, 1]
17 8 1 4.25
[8, 5, 3]
[16, 10, 6, 2]
[8, 5, 3, 1, 0, 0]
[5, 3]
[8, 5]
[0, 0, 0, 0]
[b, a, c]
[a, b, c]
6
[b, a, c, d]
名前の付け方に決まりがあります。
| 関数 | 元の配列 | 戻り値 |
|---|---|---|
sort()、reverse()、fill(0) |
書き換える | なし |
sortedArray()、reversedArray()、copyOf()、sliceArray() |
そのまま | 新しい配列 |
sorted()、reversed()、filter { }、map { } |
そのまま | List |
filter や map の結果は配列ではなく List です。 配列に戻したいときは toIntArray() や toTypedArray() を続けます。
vararg の引数に配列を渡すときは、前に *(スプレッド演算子)を付けます。arrayOf(*words, "d") のように、既存の配列に要素を足した新しい配列を作るときにも使えます。
よくある質問
Q. 配列に要素を追加するにはどうすればよいですか?
A. 配列の長さは変えられないので、a + 4 で要素を足した新しい配列を作るか、copyOf(新しい長さ) で長い配列に複製します。追加や削除を繰り返す処理は、MutableList で組み立てて最後に toIntArray() や toTypedArray() で配列にすると書きやすくなります。
Q. Array<Int> と IntArray はどちらを使えばよいですか?
A. 数値だけを入れるなら IntArray が向いています。JVM 上で int[] になり、値をそのまま並べて持てます。null を入れたいときや、型引数を使う汎用の関数に渡すときは Array<Int?> や Array<Int> を使います。2 つは別の型なので、toIntArray()・toTypedArray() で変換します。
Q. 二次元配列の列数を行ごとに変えられますか?
A. 変えられます。二次元配列は「配列の配列」なので、Array(3) { r -> IntArray(r + 1) } のように行ごとに長さの違う配列を入れられます。列数を調べるときは、行ごとに grid[r].size を見ます。
Q. 配列の中身を比べる == のような演算子はありますか?
A. 演算子はありませんが、a contentEquals b と中置で書けます。入れ子の配列は a contentDeepEquals b です。== で比べたい場合は、List に変換するか、最初から List で持つ方法があります。
まとめ
- 配列は長さ固定で、要素の書き換えはできる入れ物。
arrayOf・Array(n) { }・intArrayOf・IntArray(n)・arrayOfNulls・emptyArrayで作る Array<Int>はInteger[]、IntArrayはint[]で別の型。相互の変換はtoIntArray()とtoTypedArray()- 追加・削除するなら
MutableList、読み取り専用で渡すならList。配列は Java との相互運用や、数値を添字で大量に扱う処理に向く - 二次元配列は
Array(3) { IntArray(4) }。ラムダの中で行を作ると行ごとに独立し、外で作った行を返すと全行が同じ配列を共有する ==は同じ配列かどうかの比較。中身はcontentEquals、二次元はcontentDeepEqualsで比べるprintln(配列)は型名@ハッシュ値になる。中身はcontentToString()・joinToString()、二次元はcontentDeepToString()で表示する- 範囲外は
ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3。getOrNull・getOrElse・indicesで防げる
参考・出典
- Kotlin 公式ドキュメント「Arrays」 https://kotlinlang.org/docs/arrays.html (配列とコレクションの使い分け、プリミティブ型配列と Java の型の対応、配列の比較、入れ子の配列、スプレッド演算子)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Kotlin 配列の使い方 arrayOf・IntArrayとListの違い」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。