Kotlin

クラスとデータクラス

初期化、状態の保護、data class、interfaceを例と結果で学びます。

対象:Kotlin 2系・JDK 17以降のJVMコンソールアプリを対象にします。Android専用のAPIは使いません。

01

クラスを使う場面

同じデータへ複数の操作を行うなら、クラスにまとめると関係が明確になります。例は独立したMain.ktとして、入門と同じ方法でコンパイル・実行してください。関数、valとvar、requireを使います。

02

残高をメソッドで変更する

コンストラクターの引数はインスタンス作成時の入力です。private set は外から読み取れる一方、書き込みをクラス内部に限定します。deposit経由にすると不正な金額を防げます。

Main.kt(全体を置き換え)KOTLIN
class Account(val owner: String) {
    var balance: Int = 0
        private set
    fun deposit(amount: Int) {
        require(amount > 0) { "金額は正の整数です" }
        balance += amount
    }
}
fun main() {
    val account = Account("Mity")
    account.deposit(1000)
    println(account.owner)
    println(account.balance)
}
実行結果OUTPUT
Mity
1000

Kotlinのクラスは通常参照を共有します。val other = account と書くと同じ口座を指します。valだけでは口座内部の状態を固定できません。

03

initで作成時に検証する

init はインスタンス作成時に実行します。空の商品名や負の価格を受け付けないルールを置けます。

Main.kt(全体を置き換え)KOTLIN
class Product(val name: String, val price: Int) {
    init {
        require(name.isNotBlank()) { "商品名は必須です" }
        require(price >= 0) { "価格は0以上です" }
    }
}
fun main() {
    try {
        val product = Product("ペン", -1)
        println(product.name)
    } catch (error: IllegalArgumentException) {
        println(error.message)
    }
}
実行結果OUTPUT
価格は0以上です
04

data classとcopy

データクラスはequalsやtoString、copyなどを生成します。copyは指定した値だけ変えた別のインスタンスを作ります。

Main.kt(全体を置き換え)KOTLIN
data class Product(val name: String, val price: Int)
fun main() {
    val original = Product("ペン", 120)
    val sale = original.copy(price = 100)
    println(original)
    println(sale)
    println(original == Product("ペン", 120))
}
実行結果OUTPUT
Product(name=ペン, price=120)
Product(name=ペン, price=100)
true

copyは浅いコピーです。プロパティがMutableListのような参照なら、その中身は共有されます。次の結果で違いを確かめます。

Main.kt(全体を置き換え)KOTLIN
data class Cart(val items: MutableList<String>)
fun main() {
    val first = Cart(mutableListOf("ペン"))
    val second = first.copy()
    second.items.add("ノート")
    println(first.items)
}
実行結果OUTPUT
[ペン, ノート]
05

interfaceで共通の約束を作る

interfaceは実装する機能の約束です。: の後に適合するinterfaceを書き、override で実装します。継承で処理を増やす前に、必要な役割を小さく切り出しましょう。

Main.kt(全体を置き換え)KOTLIN
interface Named { fun label(): String }
class Member(private val name: String) : Named {
    override fun label(): String = "会員: $name"
}
fun main() {
    val member: Named = Member("Mity")
    println(member.label())
}
実行結果OUTPUT
会員: Mity
06

練習:在庫の販売

初期在庫1のStockを作り、在庫があれば1減らしてtrue、なければfalseを返すsellを実装します。負の初期在庫も検証してください。

解答例と結果を確認する

まず自分で書いてから、処理の順番と結果を比べましょう。

Main.kt(全体を置き換え)KOTLIN
class Stock(initial: Int) {
    init { require(initial >= 0) }
    var count = initial
        private set
    fun sell(): Boolean {
        if (count == 0) return false
        count -= 1
        return true
    }
}
fun main() {
    val stock = Stock(1)
    println(stock.sell())
    println(stock.sell())
    println(stock.count)
}
実行結果OUTPUT
true
false
0

公式ドキュメントで確認する:クラス / データクラス