data classとは?
data classとはその名前が示す通り、データを定義・保持することに特化したclassです。
『データ』だとカバーする範囲が広すぎて、やや曖昧でイメージしづらいと思うので、ここでは『設定項目』と言い換えてみましょう。
アプリ開発においては、様々な設定項目を定義して管理する必要がありますよね。
代表例としては、ユーザーの設定項目(ユーザー名やメールアドレスなど)や、画面表示の設定項目(カラーの切り替えや、メニューの表示・非表示など)があげられます。
こういった設定項目を定義・管理するための特別なclassが、今回のテーマである data class
というわけです。
とは言え、このような設定項目はもちろん通常の class
でも定義することができます。
たとえば、下は通常の class
でユーザーの設定項目(名前とメールアドレス)を定義した例です。
class UserData(
val name: String,
val email: String
)
fun main() {
val myUserData = UserData("Tom","mail@...")
}
別にエラーが起こるわけでもないですし、一見、何の問題もないように思えると同時に、なぜわざわざ data class
なるものが存在するのか、その存在意義に疑問を感じますよね。
ですが、実は通常の class
では設定項目を管理するにあたって、いくつか不都合な点があるんです。
次のステップでは、通常の class
と data class
の違いにスポットライトを当てていきたいと思います。
POINT!
- ・data classはデータを定義・保持するのに特化した特別なclass!
- ・データ=設定項目と言い換えるとイメージしやすい(かも)!
- ・設定項目を定義すること自体は通常のclassでもできるが…!?
通常のclassとdata classの主な違い
前章で例を示したように、単に設定項目を定義するだけであれば通常の class
でも問題ありません。
しかし、ただ『定義』するだけでなく、設定項目の値の確認や変更、比較などの『管理』も含めると、通常の class
では難しくなります。
たとえば、通常の class
で定義されたデータ(設定項目)を println()
で出力しても、人間にとって意味が分かりづらい形式で出力されてしまいます。
class UserData(
val name: String,
val email: String
)
fun main() {
val myUserData = UserData("Tom","mail@...")
println(myUserData) // UserData@7cc355be
}
『UserData@7cc355be』と出力されても、myUserData
にどのような値が設定されているのか確認しづらいですよね。
一方、data class
で定義しておくと、人間が把握しやすい形式で出力されます。
data class UserData(
val name: String,
val email: String
)
fun main() {
val myUserData = UserData("Tom","mail@...")
println(myUserData) // Outputs: UserData(name=Tom, email=mail@...)
}
『UserData(name=Tom, email=mail@…)』と出力されるようになり、だいぶ確認しやすくなりましたね!
このように data class
で定義されたインスタンスを println()
で出力すると、Kotlinは toString()
メソッドのオーバーライドを通じて、人間が把握しやすい形式に変換してくれます。
この違いだけでも、data class
で設定項目を定義する意味は大きいと言えますが、もちろん違いはこれだけではありません。
例えば、名前とメールアドレスが他のデータと重複していないことを、インスタンス同士で比較して確認する必要がある状況を想像してみて下さい。
この時、通常の class
で定義されていると、設定項目の値が完全に一致していても、2つのオブジェクトがメモリ上で同じ場所にある(同一場所を参照する)わけではないので、false
を返してしまいます。
class UserData(
val name: String,
val email: String
)
fun main() {
val myUserData = UserData("Tom","mail@...")
val otherUserData = UserData("Tom","mail@...")
println(myUserData == otherUserData) // Outputs: false
}
しかし、data class
で定義しておけば、設定項目の中身(値)に焦点を当てて比較してくれるので、開発者の思惑通り、この場合は true
を返します。
data class UserData(
val name: String,
val email: String
)
fun main() {
val myUserData = UserData("Tom","mail@...")
val otherUserData = UserData("Tom","mail@...")
println(myUserData == otherUserData) // Outputs: true
}
さらに、data class
の場合は、copy()
でインスタンスをコピーできるのも大きなメリットです。
たとえば、ユーザーがメールアドレスを変更したくなった場合、当然ながらユーザー名に関しては変更する必要がありません。むしろ、何かの拍子にユーザー名が書き換えられてしまったら大問題です。
このような場合においては、インスタンスをコピーすることで、安全に設定項目を更新することが可能です。
data class UserData(
val name: String,
val email: String
)
fun main() {
val myUserData = UserData("Tom","mail@...")
val newMyUserData = myUserData.copy(
email = "new_mail@..."
)
println(newMyUserData) // Outputs: UserData(name=Tom, email=new_mail@...)
println(myUserData) // UserData(name=Tom, email=mail@...)
}
上のコードを見てみると、newMyUserData
においては name
の値はコピーされて『Tom』になっていることが確認できます。
また、コピー元である myUserData
は、ちゃんとそのままの状態になっている(値が書き換えられていない)ことも確認できますね。
通常の class
と data class
の違いは他にもありますが、まずはこれらの違いをしっかり把握しておくことが大切です。
POINT!
- ・data classの場合、println()による出力結果が人間にとって読みやすい!
- ・data classの場合、設定項目の値によって比較してくれる!
- ・data classの場合、copy() でインスタンスを安全にコピーできる!
data classの実際の使用シーン
これまで例として紹介してきたように、data class
は何かの設定項目を定義・管理したい場合に使用されることが多いです。
とりわけAndroidアプリ開発においては、アプリのUI状態を管理する場合に、View Modelと共に利用されます。
たとえば、ボタンタップで数値をカウントできるカウントアップアプリの場合、数値の状態変化を管理する必要があるので、次のようにデータ(UI状態)を定義することができます。
data class UiState(
val count: Int = 0
)
あとはView Modelのclassで UiState
のインスタンスを初期のUI状態として定義し、カウントを増やしたり、リセットしたりするためのメソッドを追加すれば、簡易的なカウントアップアプリが出来上がります。
カウントを増やす際も、単に数値を書き換えるのではなく、直前の状態のものを copy()
でコピーしてから値を更新することで、安全にUI状態の管理を行うことができます。
こういったことが可能なのも、data class でUI状態管理の設定項目が定義されているからですね。
Androidアプリ開発におけるUI状態の管理については、こちらの記事 → 【Jetpack Compose】UI状態管理の基本と応用(ViewModel) で詳しく解説しているので、興味があればご一読ください!
POINT!
- ・Androidアプリ開発では、UI状態を管理する目的でdata classが使用されることがある!(※もちろん、他の目的に使用されることもあります)
- ・data classのインスタンスは copy() が使えるので、安全にUI状態の更新ができる!
- ・Androidアプリ開発におけるUI状態の管理について、詳しくしりたい方はこちらの記事も参照されたし!