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期連続の黒字決算。

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

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