Re:ゼロから始める文系プログラマ

未経験がプログラミングを通して人生を変える

【Kotlin入門】⑤クラス


スポンサードリンク
 

f:id:ShotaNukumizu_1000:20210627142712p:plain

おはようございます。Shotaです。

今日はKotlin文法の「クラス」について解説します。



クラス

クラスとはオブジェクト指向プログラミングにおいて、オブジェクトの設計図に相当するものです。クラスに基づいて作成されたオブジェクトの実体そのものをインスタンスと呼びます。

クラスに基づいて作成されたオブジェクトは、自身の固有のデータを持ち、自身の動作を記述できます。

クラスはオブジェクト指向プログラミングの基本的な要素です。


クラス宣言

Kotlinでは、クラスをclassを用いて宣言します。

class Color

fun main() {
    var color = Color()
    println(color)
}

▼実行結果

Color@54bedef2

上のプログラムを実行すると、「クラス名@id」の形で出力されます。

全ての親クラスのデータ型はAnyになります。(Anyとはどのようなデータ型でも扱えるデータ型)そのため、今回説明する文法はすべてAnyの中身に触れます。


クラスの初期化

Kotlinでは、クラスの初期化はコンストラク、あるいはイニシャライザを使います。


コンストラク

コンストラクタとは、オブジェクト指向プログラミングにおいてクラスをインスタンス化する際に実行される特別なメソッドです。初期化をする時にコンストラクタへ必要な情報を流すことで、新しいオブジェクトを生成できます。

コンストラクタを表現するにはconstructorを利用しますが、指定しなくても暗黙的に利用されるので省略できます。

ちなみにKotlinのコンストラクタには、プライマリーコンストラクセカンダリーコンストラクの二種類があります。

class User1 constructor()

class User2 

fun main() {
    val User1 = User1()
    val User2 = User2()
    println(User1)
    println(User2)
}

▼実行結果

User1@8efb846
User2@2a84aee7

コンストラクタの引数は必要に応じて、valvarを使えます。もちろん省略しても構いません。

class User(age: Int)
class Userval(val age: Int)
class Uservar(var age: Int)

fun main() {
    val user = User(age = 20)
    // user.ageにアクセスできない

    val userval = Userval(age = 33) // valで指定されているので上書きできない
    println(userval.age)

    val uservar = Uservar(age = 33)
    uservar.age = 10 // varで指定されているので上書きOK
    println(uservar.age)
}

このプログラムの一番上にあるUserクラスの記述を消してプログラムを実行すると次のようになります。

33
10


プライマリーコンストラク

Kotlinのプライマリーコンストラクタの定義は次の通りです。書き方は普通のクラスと同じです。

class User {
    constructor() {
        println("Primary Constructor")
    }
}

fun main() {
    val user = User()
    println(user)
}

▼実行結果

Primary Constructor
User@54bedef2


セカンダリーコンストラク

セカンダリーコンストラクタは最初に定義したプライマリーコンストラクタの下に書きます。

class User {
    
    // プライマリーコンストラクタ
    constructor() {
        println("Primary Constructor")
    }

    // セカンダリーコンストラクタ
    constructor(age: Int): this() {
        println("Primary Constructor age=$age")
    }

}

fun main() {
    val user2 = User(age=40)
    println(user2)
}

▼実行結果

Primary Constructor
Primary Constructor age=40
User@54bedef2

セカンダリーコンストラクタの処理を実行すると、プライマリーコンストラクタで定義した処理と一緒に実行されます。


イニシャライザ

イニシャライザとは、コンストラクタよりも前に実行される処理でinitを用いて実行します。更にinitはブロック宣言を用いることで初期化時に必要な処理内容を記述できます。

イニシャライザとコンストラクタの実行順序は次の通りです。

class User {

    // イニシャライザ
    init {
        println("Initializer")
    }

    // プライマリーコンストラクタ
    constructor() {
        println("Primary Constructor")
    }

    // セカンダリーコンストラクタ
    constructor(age: Int): this() {
        println("Primary Constructor age=$age")
    }
}

fun main() {
    val user1 = User()
    println("user1 = $user1")

    val user2 = User(age=30)
    println("user2 = $user2")
}

▼実行結果

Initializer
Primary Constructor
user1 = User@54bedef2
Initializer
Primary Constructor
Primary Constructor age=30
user2 = User@5caf905d

インスタンスuser1では、イニシャライザとプライマリーコンストラクタの処理が実行されます。一方で、インスタンスuser2セカンダリーコンストラクタのプロパティを指定すると、イニシャライザとプライマリーコンストラクタだけではなくセカンダリーコンストラクタの処理も実行されます。


プロパティ

プロパティとはアクセスされた時にフィールドのように振る舞い、フィールドはクラス内部で状態を保持します。

プロパティの実装はアクセサー(gettersetter)を使い、プロパティがアクセスされたときや値を割り当てたい時にアクセサーで定義された内容を実行できます。

また、プロパティはvarあるいはvalを用いて宣言でき、必ず初期化する必要があります。次の通り、インスタンスからプロパティにアクセスできます。

class Language {
    val lang = "kotlin" //プロパティの初期化が必要
    var version = 1.4
}

fun main() {
    val language = Language()
    println(language.lang)
    println(language.version)
    language.version = 1.6
    println(language.version) // varで定義されているので変更OK
}

▼実行結果

kotlin
1.4
1.6

プロパティはgetterあるいはsetterを自動的に作りますが、カスタマイズしたgettersetterも定義できます。

var宣言に対してはget()set()val宣言に対してはget()を利用してカスタマイズできます。

class Language {
    val lang get() = "kotlin"

    var version = 1.4
    get() {
        println("Get Value=$field")
        return field
    }
    set(value) {
        field = value
        println("Set Value=$field")
    }
}

fun main() {
    val language = Language()
    println(language.lang) // kotlinと表示
    println(language.version) // Get value = 1.4と表示
    language.version = 1.6 // Set value = 1.6と表示
    println(language.version) // Get value = 1.6と1.6が表示
}

▼実行結果

kotlin
Get Value=1.4
1.4
Set Value=1.6
Get Value=1.6
1.6

上のコードを見ると、fieldが利用されています。もちろん、setterだけではなくgetterでも利用できますが。これはバッキングフィールドと呼ばれる特殊なフィールドでフィールド宣言時に自動的に提供されます。

内部の値はfieldに格納されるので、settergetterで内部の値を操作したいときはfieldを利用します。


インナークラス

クラスの中にクラスを持つような構造を内部クラスといいます。外部クラスと内部クラスが分離されているので、内部クラスを隠蔽できます。はじめに単純なclassをネストする構造で挙動を説明します。

下のプログラムでは、Version.Kotlin()で直接VersionクラスからKotlinインスタンスを生成できるようになっています。ネストされたクラスは単に外部クラスの名前空間内のクラスとなっています。したがって、内部のクラスからouterVersionにアクセスできず、また外部クラスからinnerVersionにアクセスできません。

class Versions {
    var outerVersion = 1.0

    class Kotlin {
        var innerVersion = 1.6

        fun setOuterversion(version: Double) {
            // outerVersion = version // コンパイルエラー。outerVersionを参照できない。
        }
    }

    fun setInnerVersion(version: Double) {
        // innerVersion = version // コンパイルエラー。innerVersionを参照できない。
    }
}

fun main() {
    println(Versions.Kotlin().innerVersion)
}

▼実行結果(コメントアウトされている部分は除外して)

1.6

では、inner class宣言を用いて内部クラスを定義するとどのように変化するでしょうか?内部クラスからouterVersionにアクセスできるようになりました。

inner class宣言で外部クラスから内部クラスへアクセスはできませんが、内部クラスから外部クラスへアクセスできます。

class Versions {
    var outerVersion = 0.0

    inner class Kotlin {
        var innerVersion = 1.4

        fun setOuterVersion(version: Double) {
            outerVersion = version
        }
    }

    fun setInnerVersion(version: Double) {
        innerVersion = version // コンパイルエラー。innerVersionを参照できない。
    }
}

fun main() {
    println(Versions().Kotlin().innerVersion)
}

▼実行結果(コメントアウトされている部分は除外)

1.4

このように内部クラスから外部クラスへアクセスできました。


オブジェクト宣言

classではなくobjectで宣言すると、インスタンスを一つだけ保持するシングルトンクラスを生成できます。

object Versions {
    const val KOTLIN = 1.6
    const val GRADLE = 6.7
    const val JETPACK_COMPOSE = 0.1

    fun asMap() = mapOf(
        "kotlin" to KOTLIN,
        "gradle" to GRADLE,
        "jetpackCompose" to JETPACK_COMPOSE
    )
}

fun main() {
    println(Versions.KOTLIN)
    println(Versions.asMap())
}

▼実行結果

1.6
{kotlin=1.6, gradle=6.7, jetpackCompose=0.1}

クラス内にオブジェクトを配置する方法として、companion object宣言があります。

companion objectはシングルトンで作られており、内包されたプロパティや関数はクラスを介して直接アクセスできます。

class Versions {
    companion object {
        val KOTLIN = 1.6
        val GRADLE = 6.7
        val JETPACK_COMPOSE = 0.1

        fun asMap() = mapOf(
            "kotlin" to KOTLIN,
            "gradle" to GRADLE,
            "jetpackCompose" to JETPACK_COMPOSE
        )
    }
}

fun main() {
    println(Versions.KOTLIN)
    println(Versions.asMap())
}

▼実行結果

1.6
{kotlin=1.6, gradle=6.7, jetpackCompose=0.1}


抽象クラス宣言

抽象クラスを利用するには、abstractで宣言する必要があり、関数やプロパティを抽象化できます。

下のプログラムは、Kotlin実装クラスに対してLanguageという抽象クラスを継承したサンプルになります。

abstract class Language {
    abstract val version: Double

    abstract fun packageName(): String
}

class Kotlin: Language() {
    override val version = 1.4

    override fun packageName() = "example.kotlin"
}

fun main() {
    val kotlin = Kotlin()
    println(kotlin.version)
    println(kotlin.packageName())
}

▼実行結果

1.4
example.kotlin


まとめ

今日の記事ではKotlinのクラスについて解説しました。

本記事で解説した内容は次の通りです。



今日の記事はこれで終了です。

【参考】

gimo.jp

e-words.jp