対象者
- KotlinとJavaを併用する方
- Kotlin初心者
1.はじめに👀
Kotlinでリスト操作をする際、toListやsortedByの挙動について意識したことはありますか?
これらの関数は、要素数によって返す具体的な型が異なるため、Javaと連携するプロジェクトでは注意が必要です。
この記事では、KotlinとJavaの視点を交えながら以下の内容を解説します。
- KotlinのtoListとsortedByの挙動
- Java連携時の注意点
- テストケースの重要性
2.KotlinのtoListとsortedByの挙動☝️
2.1 toList()の挙動
Kotlinの toList は、コレクションから 不変リスト (List) を作成します。しかし、要素数によって実際に返される型は異なります。
実例
fun main() {
val single = sequenceOf(1).toList()
val multiple = sequenceOf(1,2,3).toList()
println(single::class) // class java.util.Collections$SingletonList
println(multiple::class) // class java.util.ArrayList
}
- 要素が1つのときはSingletonList
- 要素が複数のときはArrayList
ただし、KotlinのtoListが返す型はあくまで List< T > として扱われるため、変更操作(例:addやremove)は許可されていません。
public fun <T> Iterable<T>.toList(): List<T> {
if (this is Collection) {
return when (size) {
0 -> emptyList()
1 -> listOf(if (this is List) get(0) else iterator().next())
else -> this.toMutableList()
}
}
return this.toMutableList().optimizeReadOnlyList()
}
2.2 sortedBy()の挙動
sortedByもtoListと同様に、要素数によって返される型が異なります。
実例
fun main() {
val single = listOf(1).sortedBy{ it }
val multiple = listOf(1,2,3).sortedBy{ it }
println(single::class) // class java.util.Collections$SingletonList
println(multiple::class) // class java.util.Arrays$ArrayList
}
- 要素数が1つの場合はSingletoList
- 要素数が複数の場合はArrays$ArrayList
public inline fun <T, R : Comparable<R>> Iterable<T>.sortedBy(crossinline selector: (T) -> R?): List<T> {
return sortedWith(compareBy(selector))
}
public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {
if (this is Collection) {
if (size <= 1) return this.toList()
@Suppress("UNCHECKED_CAST")
return (toTypedArray<Any?>() as Array<T>).apply { sortWith(comparator) }.asList()
}
return toMutableList().apply { sortWith(comparator) }
}
これもListとして返されるため、不変リストとしての振る舞いをします。変更操作を行おうとするとエラーになります。
2.3 Kotlinリスト設計の背景
Kotlinの公式ドキュメントでは不変コレクションがデフォルトとして提供されています。
https://kotlinlang.org/docs/collections-overview.html
3.Java連携時の注意点⚠️
Kotlinが返すリスト(toListやsortedByの結果)は不変リストとして設計されています。
しかし、Javaではその内部実装に依存して動作が異なります。
3.1Kotlinの不変リストに対する変更操作
Kotlinコード
fun createSortedList(): List<Int> {
return listOf(3,1,2).sortedBy(){ it }
}
Javaコード
List<Integer> list = KotlinClassKt.createSortedList();
list.add(4); // UnsupportedOperationExceptionが発生
複数要素のsortedByの内部実装はArrays.ArrayList
のため、addやremoveを実行するとUnsupportedOperationExceptionが発生します。
Arrays.ArrayListはjava.util.ArrayListと似ていますが、java.util.Arrays内部で作られる一時的なリストで、固定サイズのリストとして動作します。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Arrays.html
Kotlinからの操作ではListの不変性を守りますが、Javaからアクセスするときの挙動に注意が必要です。
3.2 Javaからアクセスする場合の矛盾点
toListの要素数が複数件の場合、内部実装がArrayListを返すため、Java側では変更可能なリストのように扱えます。
Kotlinコード
fun createList(): List<Int> {
return sequenceOf(1, 2, 3).toList()
}
Javaコード
List<Integer> list = KotlinClassKt.createList();
list.add(4); // 実行可能(エラーなし)
KotlinとJavaでリストの扱いが矛盾する可能性があります。
Kotlinでは不変リストとして設計されていますが、Javaからアクセスすると、リストの変更が試みられることがあります。これはKotlinとJavaのAPI設計の違いに起因しています。
Kotlin側でtoListを使った場合、返されるリストが実際に変更可能であるとは限らない場合もあります。
KotlinとJavaで挙動に矛盾が生じないよう、不変リストをJava側でも守るべきです。
4.テストケースの重要性🧪
要素数やシナリオごとに挙動を確認するテストを用意することで意図しない動作を防ぐことが大切です。
例
fun main(){
val single = listOf(1).sortedBy { it }
val multiple = listOf(3, 1, 2).sortedBy { it }
// 要素数1の挙動を確認
assert(single == listOf(1))
// 要素数複数の挙動を確認
assert(multiple == listOf(1, 2, 3))
5.まとめ
-
KotlinのtoListやsortedByは不変リストを返しますが、要素数によって内部実装が異なる
-
JavaからKotlinのリストを扱う際、不変リストと変更可能リストを区別する必要がある
-
テストケースを使って挙動を確認することで、予期せぬ動作を防ぐ
KotlinのListは不変がデフォルトで、JavaのListとは挙動が異なります。この違いを意識することが大切です。
何か間違ったことを言っている場合はご指摘よろしくお願いします。