1. 概要

このチュートリアルでは、比較します配列リスト Kotlinで。 そのために、それらの違いをさまざまなグループに分類します。 次に、各グループについて詳しく説明します。

2. 配列対。 リスト

2.1. データ構造

データ構造の観点から、Kotlinコンパイラは配列をコンパイルします JVM配列として入力 。 したがって、Kotlinの配列も、同じタイプの要素の固定サイズのシーケンスです。 これは、要素の数は常に配列の作成時に決定され、変更できないためです。

たとえば、簡単な例を考えてみましょう。

val cities = arrayOf("Tehran", "Sari", "Neka")

Kotlinコンパイラ( kotlinc )は、これをバイトコードにコンパイルします。 javap を使用して表示すると、次のようになります。

0: iconst_3
1: anewarray     #8     // class java/lang/String

anewarray opcodeは、参照型の配列を作成します。 また、iconst_3は整数3をオペランドスタックにプッシュします。 後で、 anewarray 命令は、この整数を配列の長さとして使用します。

一方、リストおよびMutableList タイプは、実装が異なるインターフェースです。 。 たとえば、 配列リスト実装は、タイプの要素のシーケンスです T それは時間とともに成長する可能性があります。 この実装は内部で配列を使用しているため、ほとんどの実行時の特性(たとえば、インデックス作成の時間計算量)は配列の特性と似ています。

に加えて配列リスト LinkedList 実装では、一連のリンクされた要素を使用して、異なるランタイム特性で同じコントラクトを実現します。 たとえば、要素の追加は常に O(1)ですが、インデックス作成は O(n)。と同じくらい悪い場合があります。

Kotlinの配列とは対照的に、 Kotlinのリストのメモリ表現は、完全に具体的な実装に依存するため、変化する可能性があります

2.2. 可変性

Kotlinの配列は常に変更可能です。これは、各セグメントの値を新しいものに置き換えることができるためです。

cities[0] = "Berlin"

ただし、配列は固定サイズであるため、配列に新しい値を追加したり追加したりすることはできません。

一方、 リストインターフェイスは不変の操作のみを提供します 。 したがって、既存の値を置き換えたり、新しい値を追加したりすることはできません。

可変性が必要な場合は、 MutableList Kotlinで。 と MutableList 、可能なすべての方法でリストを変更することが可能です。

val colors = mutableListOf("Blue") // [Blue]
colors[0] = "Green" // replace: [Green]
colors.add(0, "Red") // prepend: [Red, Green]
colors.add("Blue") // append: [Red, Green, Blue]

とは対照的に配列リスト MutableList それが提供するように、サイズが拡大または縮小することができます追加()削除する() メソッド。

2.3. 一般的な分散

ジェネリックに関する分散の概念は、同じベースタイプと異なるタイプパラメーターを持つジェネリックタイプが互いにどのように関連するかを決定します

たとえば、StringがKotlinのAnyのサブタイプであることは誰もが知っています。 それを考えると、 リストリスト ? Kotlinでは、 リストと定義されている:

public interface List<out T> : Collection<T> {
    override fun iterator(): Iterator<T>
    public operator fun get(index: Int): T

    // omitted
}

タイプ引数Tは常にoutの位置で使用されるため、リスト共変です 。 つまり、 リストサブタイピングの関係を保持します。 リストのサブタイプですリスト

val colors = listOf("Red")
val colorsAsAny: List<Any> = colors // this works

共分散のため、ここでは次の値を割り当てています。 リストタイプの変数にリスト

一方、同じことは当てはまりません MutableList 、typeパラメータが両方で使用されているためアウトポジション:

public interface MutableList<T> : List<T>, MutableCollection<T> {
    override fun add(element: T): Boolean
    override fun remove(element: T): Boolean
    // omitted
}

だから MutableList 不変です。 その結果、 MutableList とは関係ありません MutableList

val colors = mutableListOf("Red")
val colorsAsAny: MutableList<Any> = colors

不変性のため、上記のスニペットはコンパイルすらしません。 非常に似ています MutableList 配列また不変です:

val colors: Array<Any> = arrayOf<String>("Red")

これらのタイプの違いは、特にJavaのバックグラウンドから来ている場合、混乱を招く可能性があります。 Javaでは、ジェネリック型は不変であり、配列は共変です。

2.4. 特殊なプリミティブ

Kotlinには、プリミティブ型の配列を作成するための特別な関数があります。

val bytes = byteArrayOf(42)
val shorts = shortArrayOf(42)
val ints = intArrayOf(42)
val longs = longArrayOf(42)
val floats = floatArrayOf(42.0f)
val doubles = doubleArrayOf(42.0)
val chars = charArrayOf('a')
val booleans = booleanArrayOf(true)

これは主に、JVMにそのような配列を作成および操作するための特別なオペコードがあるためです。

0: iconst_1
1: newarray  byte

ここで、バイトコードは newarray opcode( anewarray ではなく)を使用してバイトの配列を作成します。 つまり、配列はプリミティブデータ型の表現を最適化しており、パフォーマンスに敏感な場合に適しています。

一方、Kotlin標準ライブラリのリストには、プリミティブ用に最適化されたバージョンはありません。

2.5. その他の微妙な違い

Javaとの良好な相互運用性を実現するために、Kotlinは一部のJavaタイプを特別に扱います。 このようなタイプは、Javaから「そのまま」ロードされるのではなく、対応するKotlinタイプにマップされます。 すなわち、 リスト MutableList それらの中にありますマップされたタイプ 。 同様に、 配列マップされたタイプでもありますが、 ルールが違います

これらすべてに加えて、2つの間にさらに微妙な違いがいくつかあります。 たとえば、“ ==” 演算子は、配列に参照の同等性を使用し、リストにコンテンツの同等性を使用しています。

println(intArrayOf(1) == intArrayOf(1)) // false
println(listOf(1) == listOf(1)) // true

3. 結論

この記事では、Kotlinの配列とリストの違いを列挙しました。 要約すると、配列は要素の固定サイズのシーケンスであり、ジェネリックに関しては可変で不変であり、より最適化されたプリミティブバージョンが付属しています。 さらに、アレイに対するJVMの特別な処理を利用しています。

一方で、 リスト MutableList 数十の具体的な実装とのインターフェースです。 それらは表現の点でより柔軟性があり、変更可能な場合はサイズが拡大または縮小する可能性があります。