0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SnapshotStateListのtoString / equals / hashCodeの罠

0
Last updated at Posted at 2026-02-18

はじめに

Composeで List を扱うとき、mutableStateListOf で生成される SnapshotStateList を利用することは多いでしょう。

しかし SnapshotStateListtoString / equals / hashCode の挙動は、一般的な List とは異なります。この違いを理解していないと、比較処理などで意図しない挙動に遭遇します。

本記事では、次の観点について解説します:

  • Kotlinの List について
  • SnapshotStateList の性質
  • なぜ toString / equals / hashCode で差が出るのか
  • SnapshotStateList の扱い方

※ 同様の注意点は SnapshotStateSetSnapshotStateMap にもあります。

List の仕様

List は要素の順序を保持するコレクション型であり、interface として定義されています。そのため、具体的な実装は複数存在し、独自実装も可能です。

Kotlinのドキュメントでは、ListtoString / equals / hashCode について明確な記述があります。

要点は次の通りです:

  • toString は要素を順序通りに並べた文字列表現を返す
  • equals は要素数・順序・各要素の equals に基づいて比較する
  • hashCode は各要素の hashCode を組み合わせて算出する
ドキュメントの原文
  • List.toString should return a string containing string representation of contained elements in exact same order these elements are stored within the list.
  • List.equals should consider two lists equal if and only if they contain the same number of elements and each element in one list is equal to an element in another list at the same index. Unlike some other equals implementations, List.equals should consider two lists equal even if they are instances of different classes; the only requirement here is that both lists have to implement List interface.
  • List.hashCode should be computed as a combination of elements' hash codes using the following algorithm:
    var hashCode: Int = 1
    for (element in this) hashCode = hashCode * 31 + element.hashCode()
    

つまり、List は構造的等価(structural equality)で比較されます。

生成過程が異なっていても、要素が同じであれば等しいと判定されます:

val list1 = mutableListOf("a", "b")
val list2 = mutableListOf("a").also { it.add("b") }

list1 == list2 // true

SnapshotStateList とは何か

SnapshotStateListandroidx.compose.runtime.snapshots に含まれるComposeの状態管理向け MutableList 実装です。

この型はSnapshotシステムと連携するため、一般的な List 実装と違う挙動を持ちます。

toString / equals / hashCode の違い

SnapshotStateList では、一般的な List のような要素ベースの toString / equals / hashCodeを持ちません。代わりに、インスタンスの参照をベースにした実装になっています。

また、Kotlinの == 演算子は、左辺の equals メソッドを呼び出すため、SnapshotStateList と一般的な List を比較すると、一方は要素ベースで、もう一方は参照ベースで比較されることになります。

val list = listOf("a", "b")
val snapshotStateList = mutableStateListOf("a", "b")

list.toString()                 // [a, b]
snapshotStateList.toString()    // SnapshotStateList(value=[a, b])@88539910

list.hashCode()                 // 4066
snapshotStateList.hashCode()    // 88539910

list == snapshotStateList       // true
snapshotStateList == list       // false

一般的な List として扱うには

SnapshotStateList を要素ベースで扱いたい場合は、toList() によって得られる List を利用すると良いでしょう。

toList() で取得した List は読み取り専用として扱え、toString / equals / hashCode も一般的な List と同様に利用できます。また、取得後に SnapshotStateList 側を変更しても、すでに取得した List の内容は変わりません。

val snapshotStateList = mutableStateListOf("a", "b")
val immutableList = snapshotStateList.toList()

immutableList.toString()             // [a, b]
immutableList.hashCode()             // 4066
immutableList == listOf("a", "b")    // true

まとめ

  • 一般的な ListtoString / equals / hashCode は要素ベースで扱われる
  • SnapshotStateListtoString / equals / hashCode は参照ベースで扱われる
  • 要素比較が必要なら toList() により、一般的な List と同様に扱う

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?