Kotlin:classおよびdata classのequals( )関数の動作

投稿日:  更新日:

Kotlinにおいて、各クラスのインスタンスに自動生成されるequals()関数は、classとdata classで比較対象に違いがあります。

紛らさしいので、まとめます。

※環境:Android Studio Giraffe | 2022.3.1 Patch 1
    Kotlin 1.8.10

スポンサーリンク

queals( )関数の違い

class及びdata classのオブジェクトを作成すると、インスタンスにequals( )関数が自動生成されます。

同じequals( )関数ですが、両者の動作は異なります。

デフォルト関数classdata class
equalsインスタンスの同一性
 true:同じインスタンス
 false:異なるインスタンス
 
プロパティの同値性
 true:同じ値
 false:異なる値
※比較はコンストラクタ引数のみ
hashCodeインスタンス全体から計算プロパティ値から計算
スポンサーリンク

例:classのequals( )関数

classの動作例です。下記のサンプルclassを用いています。

class HumanInfo( var name: String = "", var age: Int = 0)

プロパティ値が同じ

    var _classA = HumanInfo(name = "AAA", age = 10)
    var _classB = HumanInfo(name = "AAA", age = 10)
    var _classC = _classA
    Log.i(TAG, "ClassA[0x%08x]".format(_classA.hashCode()))
    Log.i(TAG, "ClassB[0x%08x]".format(_classB.hashCode()))
    Log.i(TAG, "ClassC[0x%08x]".format(_classC.hashCode()))
    Log.i(TAG, "Class A==B ${_classA.equals(_classB)}")
    Log.i(TAG, "Class A==C ${_classA.equals(_classC)}")
ClassA[0x02536a1d]		... hashはインスタンスから計算
ClassB[0x07473492]		... 同上
ClassC[0x02536a1d]
Class A==B false		... 異なるインスタンス
Class A==C true

プロパティ値が異なる

    var _classA = HumanInfo(name = "AAA", age = 10)
    var _classD = HumanInfo(name = "DDD", age = 40)
    Log.i(TAG, "ClassA[0x%08x]".format(_classA.hashCode()))
    Log.i(TAG, "ClassD[0x%08x]".format(_classD.hashCode()))
    Log.i(TAG, "Class A==D ${_classA.equals(_classD)}")
ClassA[0x02536a1d]
ClassD[0x06420e63]
Class A==D false		... 異なるインスタンス

プロパティ値を変更(異なる値へ)

    var _classA = HumanInfo(name = "AAA", age = 10)
    var _classE = HumanInfo(name = "AAA", age = 10)
    Log.i(TAG, "ClassA[0x%08x]".format(_classA.hashCode()))
    Log.i(TAG, "ClassE[0x%08x]".format(_classE.hashCode()))
    Log.i(TAG, "Class A==E ${_classA.equals(_classE)}")
    _classE.apply { name = "EEE" }					... プロパティ値を変更
    Log.i(TAG, "ClassA[0x%08x]".format(_classA.hashCode()))
    Log.i(TAG, "ClassE[0x%08x]".format(_classE.hashCode()))
    Log.i(TAG, "Class A==E ${_classA.equals(_classE)}")
ClassA[0x02536a1d]
ClassE[0x0e147a60]
Class A==E false		... 異なるインスタンス
ClassA[0x02536a1d]
ClassE[0x0e147a60]
Class A==E false		... 異なるインスタンス

プロパティ値を変更(同じ値へ)

    var _classA = HumanInfo(name = "AAA", age = 10)
    var _classF = HumanInfo(name = "FFF", age = 60)
    Log.i(TAG, "ClassA[0x%08x]".format(_classA.hashCode()))
    Log.i(TAG, "ClassF[0x%08x]".format(_classF.hashCode()))
    Log.i(TAG, "Class A==F ${_classA.equals(_classF)}")
    _classF.apply { name = "AAA"; age = 10}			... プロパティ値を変更
    Log.i(TAG, "ClassA[0x%08x]".format(_classA.hashCode()))
    Log.i(TAG, "ClassF[0x%08x]".format(_classF.hashCode()))
    Log.i(TAG, "Class A==F ${_classA.equals(_classF)}")
ClassA[0x02536a1d]
ClassF[0x02418b19]
Class A==F false		... 異なるインスタンス
ClassA[0x02536a1d]
ClassF[0x02418b19]
Class A==F false		... 異なるインスタンス
スポンサーリンク

例:data classのequals( )関数

data classの動作例です。下記のサンプルclassを用いています。

data class PersonalInfo( var name: String = "", var age: Int = 0)

プロパティ値が同じ

    var _dataA = PersonalInfo(name = "AAA", age = 10)
    var _dataB = PersonalInfo(name = "AAA", age = 10)
    var _dataC = _dataA
    Log.i(TAG, "DataA[0x%08x]".format(_dataA.hashCode()))
    Log.i(TAG, "DataB[0x%08x]".format(_dataB.hashCode()))
    Log.i(TAG, "DataC[0x%08x]".format(_dataC.hashCode()))
    Log.i(TAG, "Data A==B ${_dataA.equals(_dataB)}")
    Log.i(TAG, "Data A==C ${_dataA.equals(_dataC)}")
DataA[0x001e8809]		... hashはプロパティ値から計算
DataB[0x001e8809]		... 同上
DataC[0x001e8809]
Data A==B true			... 同じ値
Data A==C true

プロパティ値が異なる

    var _dataA = PersonalInfo(name = "AAA", age = 10)
    var _dataD = PersonalInfo(name = "DDD", age = 40)
    Log.i(TAG, "DataA[0x%08x]".format(_dataA.hashCode()))
    Log.i(TAG, "DataD[0x%08x]".format(_dataD.hashCode()))
    Log.i(TAG, "Data A==D ${_dataA.equals(_dataD)}")
DataA[0x001e8809]
DataD[0x001ff0e4]
Data A==D false			... 異なる値

プロパティ値を変更(異なる値へ)

    var _dataA = PersonalInfo(name = "AAA", age = 10)
    var _dataE = PersonalInfo(name = "AAA", age = 10)
    Log.i(TAG, "DataA[0x%08x]".format(_dataA.hashCode()))
    Log.i(TAG, "DataE[0x%08x]".format(_dataE.hashCode()))
    Log.i(TAG, "Data A==E ${_dataA.equals(_dataE)}")
    _dataE.apply { name = "EEE" }				... プロパティ値を変更
    Log.i(TAG, "DataA[0x%08x]".format(_dataA.hashCode()))
    Log.i(TAG, "DataE[0x%08x]".format(_dataE.hashCode()))
    Log.i(TAG, "Data A==E ${_dataA.equals(_dataE)}")
DataA[0x001e8809]
DataE[0x001e8809]
Data A==E true			... 同じ値
DataA[0x001e8809]
DataE[0x00206905]
Data A==E false			... 異なる値

プロパティ値を変更(同じ値へ)

    var _dataA = PersonalInfo(name = "AAA", age = 10)
    var _dataF = PersonalInfo(name = "FFF", age = 60)
    Log.i(TAG, "DataA[0x%08x]".format(_dataA.hashCode()))
    Log.i(TAG, "DataF[0x%08x]".format(_dataF.hashCode()))
    Log.i(TAG, "Data A==F ${_dataA.equals(_dataF)}")
    _dataF.apply { name = "AAA"; age = 10}			... プロパティ値を変更
    Log.i(TAG, "DataA[0x%08x]".format(_dataA.hashCode()))
    Log.i(TAG, "DataF[0x%08x]".format(_dataF.hashCode()))
    Log.i(TAG, "Data A==F ${_dataA.equals(_dataF)}")
DataA[0x001e8809]
DataF[0x0020e176]
Data A==F false			... 異なる値
DataA[0x001e8809]
DataF[0x001e8809]
Data A==F true			... 同じ値
スポンサーリンク

関連記事:

KDocのドキュメント記述例を紹介します。 KDocを使えばKotlinのソースコードに書いたコメントからドキュメントを作成することが出来ます。後々、クラスの再利用を考えているなら、ドキュメントを残して再利用性を高めておきましょう! ...
Kotlinは関数を変数に代入したり、引数で受け渡したりできます。 関数を第一級オブジェクトで扱えるためです。 これにより、関数を使った処理の委譲が容易になりました。ArrayOf#forEach( )がその典型的な例です。 以上のような操作で必要となる「関数オブジェクト」について、まとめます。 ...
クロージャ(Closure)は新しい概念ではなく、関数型プログラミングに古くから存在していました。 手続き型やオブジェクト指向プログラミングにも、だいぶ前から採用が進んでいます。 Kotlinもクロージャが使える言語の1つです。 ...
Androidのアプリを作成しているとSAM変換が頻繁に登場します。 プログラムが簡素に記述できることから、プログラマーに好評なようです。 Kotlinが関数を第一級オブジェクトとして扱える恩恵です。 しかし、私はSAM変換に出合うと、いつもプログラムの論理が把握できなくて戸惑います。なので、苦手です。あまり好きではありません。 苦手を克服するために、もっとSAM変換の理解を深めたいと思い、まとめてみました。 ...
近頃の携帯端末はクワッドコア(プロセッサが4つ)やオクタコア(プロセッサが8つ)が当たり前になりました。 サクサク動作するアプリを作るために、この恩恵を使わなければ損です。 となると、必然的に非同期処理(マルチスレッド)を使うことになります。 JavaのThreadクラス、Android APIのAsyncTaskクラスが代表的な手法です。 Kotlinは上記に加えて「コルーチン(Coroutine)」が使えるようになっています。 今回は、このコルーチンについて、まとめます。 ...
Android Studio Giraffe(2023.07)になって、ビルドスクリプトの推奨がKotlin DSLになりました。 この機会に、ビルドスクリプトと記述言語についてまとめます。 ※環境:Android Studio Giraffe | 2022.3.1    :Android Gradle Plugin 8.1.0    :Gradle 8.0 ...
KotlinはJavaのプリミティブ型に当たるデータ型を持ちません。しかし、同等な記述が基本データ型を用いて出来ます。 Kotlinにおいて、基本データ型がどのように扱われているのか、まとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10 ...
継承を行うことなく新しい機能(関数やプロパティ)をクラスへ追加する「拡張機能」についてまとめます。 何処にでも手軽に定義できるため、とても重宝する仕組みですが、乱用するとプログラムが無法地帯になるので、管理は重要です。そのために、十分な理解が必要になります。 ちなみに、Android Jetpackは、この「拡張機能」を活用して作られた部分が多くあります。「拡張機能」無くして、成り立たないシステムです。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 1     Kotlin 1.8.10 ...
スコープ関数はKotlinの標準ライブラリで提供されています。 そのことから、Kotlinを特徴付ける重要な構文であることが分かります。 ただし、「プログラムの動作を定義する構文」ではなく、「プログラムのコードの品質を上げる構文」です。 同様な動作はスコープ関数を使わなくても記述できます。しかし、スコープ関数を使えばスマートな(洗練された)記述になります。 積極的に使いたいと思いますが、applyとlet以外はあまり使う機会がありません。また、稀にwithなどが登場すると、コードの流れが理解できずに戸惑います。 ですので、スコープ関数について、まとめました。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1 ...
スポンサーリンク