【第28章】データクラスについて詳しく解説

【第28章】データクラスについて詳しく解説 基礎勉強
スポンサーリンク
リンコ
リンコ

こんにちは!リンコですピヨ!この記事では自分の勉強の復習もかねて、Kotlinでのデータクラスを記述する方法を解説するピヨ!

この第28章では、プログラミング言語であるKotlinのデータクラスについて、一緒に勉強していきましょう。

この記事を読めばプログラミング未経験の方も、Kotlin入門レベルのデータクラスについて1つ1つ理解しながら勉強できると思うので、是非最後まで読んで頂ければと思います。

※この記事で出てくる「サンプルコード」は、記述が長く画面からはみ出ている場合がありますが、横にスライドすると表示されるのでご安心ください。

スポンサーリンク

【Kotlin】データクラスについて

データクラスについて

まずは、データクラスについてザックリ理解していきましょう。

データクラスとは?

Kotlinでは、何かしらのデータを格納しておくクラスのことを「データクラス」と呼び、言語の仕様として用意されています。

データクラスはどんな時に必要?

データクラスは、データを格納しておく時に必要なのはもちろんですが、データの内容が同じかどうか比較する際や、特にAnyクラスの3つの関数を実装する際にも役立ちます。

しかし、これまでに勉強してきた「通常のクラス」を使ってもデータクラスみたいなものを作れそうな気もしますよね。
クラスの中に複数のプロパティ変数を用意し、その変数にいろいろ値を書き込んだり、変数をクラス内にひとまとめにしておけばそのクラスをデータクラスとして使えそうです。

ですが、Kotlinには言語の仕様として「データクラス」が用意されていますが、それはなぜでしょうか?

まずは、Kotlin側で用意されているデータクラスを使わないで、通常のクラスでデータクラスっぽいものを作ってみます。

リンコ
リンコ

データクラスはAnyクラスの3つの関数をオーバーライドして実装する際に便利ピヨ!

【Kotlin】通常のクラスでデータクラスっぽいものを作ってみる

通常のクラスでデータクラスっぽいものを作ってみる

以下ではデータクラスを使わず、通常のクラスでデータクラスっぽいものを作り、データを比較するコードを記述していきます。

引き渡すパラメータが違うインスタンスで比較

まずは、引き渡すパラメータが違うインスタンスで比較していきます。

サンプルコード

このサンプルコードでは、今まで継承クラスとして使ってきた「Rinkoクラス」を基底クラスとし、Rinkoクラスをデータクラスと見立てて名前と年齢と誕生日を格納できるようにします。

そしてこのRinkoクラスから2つインスタンスを生成し、名前と年齢と誕生日をセットしていき、その2つのインスタンスが「同じもの」かどうかを判定するコードを記述していきます。

fun main() {
    val rinko1 = Rinko("リンコ", 10, 23, 4)
    val rinko2 = Rinko("ここまる", 30, 23, 12)
    if(rinko1==rinko2) {
        println("2人は同一人物です")
    } else {
        println("2人は別人です")
    }
}

class Rinko(val name: String, val age: Int, val birthday: Int,val birthmonth: Int)

出力

サンプルコードを実行すると、以下のように出力されます。

2人は別人です

上記のコードでは、インスタンスrinko1とrinko2を比較する記述をしましたが「2人は別人です」と出力されました。

引き渡されているパラメータが違うので当然ですね。

引き渡すパラメータが同じインスタンスで比較

続いて、引き渡すパラメータが同じインスタンスで比較していきますが、これだとどうなるでしょうか?

サンプルコード

このサンプルコードでは、基本的に上記と同様のコードですが、引き渡すパラメータのみ少し変更しています。

fun main() {
    val rinko1 = Rinko("リンコ", 10, 23, 4)
    val rinko2 = Rinko("リンコ", 10, 23, 4)
    if(rinko1==rinko2) {
        println("2人は同一人物です")
    } else {
        println("2人は別人です")
    }
}

class Rinko(val name: String, val age: Int, val birthday: Int,val birthmonth: Int)

出力

サンプルコードを実行すると、以下のように出力されます。

2人は別人です

パラメータが違うインスタンスを比較すると「2人は別人です」と出力されるのは当然だと思いましたが、パラメータが同じインスタンスを比較しても「2人は別人です」と出力されました。

なぜfalseとして出力されたのでしょうか?それは、内容は同じでもインスタンスとしては別のものだからです。「同じデータからコピー機で用紙に2枚印刷した」と言ったイメージですね。

これは第26章で勉強したAnyクラスが必ず持っている「equals関数」の影響によって起こるものです。

2つのインスタンスを「==」で比較する場合、裏側でAnyクラスのequals関数が呼ばれて比較の処理が実行されています。

Anyクラスのequals関数の仕様は「インスタンスとして同じ」かどうかを判定するように作られているので、今回のように「データの中身が同じ」かどうかは判定されませんでした。

リンコ
リンコ

データの中身は同じだけど、インスタンスとして違うのでfalseになるピヨ!

データの中身を同じかどうか判断されるようにするには?

では、インスタンスのパラメータも全て同じかどうかを判定したい場合はどうすればいいでしょうか?

その為には、equals関数をオーバーライドして、equals関数の仕様を書き換える必要があります。

サンプルコード

以下のサンプルコードでは、2つのインスタンスの比較結果が同じになるように、equals関数の仕様を書き換えています。

fun main() {
    val rinko1 = Rinko("リンコ", 10, 23, 4)
    val rinko2 = Rinko("リンコ", 10, 23, 4)
    if(rinko1==rinko2) {
        println("2人は同一人物です")
    } else {
        println("2人は別人です")
    }
}

class Rinko(val name: String, val age: Int, val birthday: Int,val birthmonth: Int) {
    override fun equals(other: Any?): Boolean {
        if(other!= null && other is Rinko && this.name == other.name && this.age == other.age && this.birthday == other.birthday && this.birthmonth == other.birthmonth) {
            return true
        }
        return false
    }
}

出力

サンプルコードを実行すると、以下のように出力されます。

2人は同一人物です

なんかかなりややこしくなりましたね(笑)

このコードでは「&&」を使って複数の条件を設定し、全ての条件が成立した場合のみtrueを返し、1つでもfalseであればfalseを返すようにしています。

そして名前、年齢、誕生日、誕生月を独自に比較し、全て同じだったためtrueになり「2人は同一人物です」と出力されました。

このように、Anyクラスのequals関数をオーバーライドして自力で「データの中身が同じ」というように判断させるには、かなりの労力がかかってしまいます。

かなりややこしいコードになりましたし労力もかかりましたが、これには1つ問題があります。equals関数をオーバーライドする際には、守らなければいけないルールがあるんです。

Kotlinではequals関数をオーバーライドした際、hashCode関数も適切に書き換えなければいけないというルールがあります。このハッシュコードも第26章で勉強したAnyクラスの関数でしたね。

equals関数をオーバーライドして書き換え、2つのインスタンスを同じにするなどの処理をした時は、hashCodeも書き換えて同じ値を返す必要があります。

ハッシュコードは、それぞれのインスタンスが持つ「固有の値(ハッシュ値)」なのですが、今の段階ではhashCodeにズレが起こってしまいます。以下のコードを見てみましょう。

サンプルコード

以下のサンプルコードでは、2つのインスタンスのハッシュコードを見比べています。

fun main() {
    val rinko1 = Rinko("リンコ", 10, 23, 4)
    val rinko2 = Rinko("リンコ", 10, 23, 4)
    
    println("rinko1のハッシュコードは「"+ rinko1.hashCode() +"」です。")
    println("rinko2のハッシュコードは「"+ rinko2.hashCode() +"」です。")
    
}

class Rinko(val name: String, val age: Int, val birthday: Int,val birthmonth: Int) {
    override fun equals(other: Any?): Boolean {
        if(other!= null && other is Rinko && this.name == other.name && this.age == other.age && this.birthday == other.birthday && this.birthmonth == other.birthmonth) {
            return true
        }
        return false
    }
}

出力

サンプルコードを実行すると、以下のように出力されます。

rinko1のハッシュコードは「1510467688」です。
rinko2のハッシュコードは「1072591677」です。

このようにequals関数をオーバーライドしたおかげでtrue判定になっているコードですが、2つのインスタンスのハッシュコードは全く違う数字になっています。

Kotlinのルール上、equals関数の比較結果がtrueになる場合、ハッシュコードも同じにならなければなりません。

しかし、ハッシュコードを正しくオーバーライドするのは、equals関数以上にかなり複雑で面倒な作業になってしまいます。

ですがKotlinではこの面倒な作業を簡単に済ませることができます。それが本題の「データクラス」という仕様です。

リンコ
リンコ

equals関数を使えばインスタンスとしてもtrueになるけど、ハッシュコードも修正しないといけないピヨ!

【Kotlin】データクラスを定義してみよう

データクラスを定義してみよう

最後にデータクラスの定義の仕方について勉強していきます。

データクラスの定義方法

データクラスはどのように定義するのでしょうか?

データクラスの定義の仕方はかなり簡単で、クラスの定義の際にクラス名の先頭に「data」と記述するだけでオッケーです。

「data」と記述すれば、自動的にequals関数とhashCode関数がオーバーライドされて実装したことになります。

上記で勉強した複雑で面倒なコードを記述する必要がなくなるのです。

実際にデータクラスを定義してみよう

では最後に、実際にデータクラスを定義してみましょう。

サンプルコード

以下のサンプルコードでは、上記までと同じコードをデータクラスとして定義します。

fun main() {
    val rinko1 = Rinko("リンコ", 10, 23, 4)
    val rinko2 = Rinko("リンコ", 10, 23, 4)
    
    println("rinko1のハッシュコードは「"+ rinko1.hashCode() +"」です。")
    println("rinko2のハッシュコードは「"+ rinko2.hashCode() +"」です。")
    
    if(rinko1==rinko2) {
        println("2人は同一人物です")
    } else {
        println("2人は別人です")
    }
}

data class Rinko(val name: String, val age: Int, val birthday: Int,val birthmonth: Int)

出力

サンプルコードを実行すると、以下のように出力されます。

rinko1のハッシュコードは「1071097741」です。
rinko2のハッシュコードは「1071097741」です。
2人は同一人物です

このようにデータクラスを使うと、Anyクラスの3つの関数を自分でオーバーライドすることなく、簡単に実装することができます。

データを格納するクラスを作る時や、Anyクラスの「toString関数」「equals関数」「hashCode関数」を実装する時は、是非データクラスを活用していきましょう。

リンコ
リンコ

データクラスにするには「data」をクラスの先頭に記述するだけで簡単ピヨ!

まとめ

まとめ

この記事では、プログラミング言語であるKotlinのデータクラスについて勉強していきましたが、いかがでしたでしょうか?今回の記事をまとめると以下のようになります。

  • Anyクラスの3つの関数を実装する際に便利
  • equals関数やhashCode関数をオーバーライドして実装してもいいが面倒
  • データクラスは「data」とクラスの先頭に記述するだけでオッケー

今回勉強した「データクラス」はKotlinの入門レベルの知識になるので、何回も読み直してしっかり覚えておきましょう。

リンコ
リンコ

次回の記事では「ローカル変数とスコープ」について勉強していくピヨ!

プログラミング未経験の方や入門レベルの方、Kotlinについて詳しくなりたい方は、また一緒に勉強するピヨ!

コメント

タイトルとURLをコピーしました