Kotlinのプロパティ活用法!初心者向けの20選

Kotlin言語とプロパティのテキスト、プログラムコードのイラストKotlin
この記事は約24分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めば、Kotlinのプロパティを使って、もっと効果的なプログラミングをすることができるようになります。

Kotlinのプロパティは、変数にアクセスするための便利な方法を提供しており、その使い方をマスターすれば、より読みやすく、保守性の高いコードを書くことができるでしょう。

初心者から経験者まで、それぞれのレベルに合った情報をご紹介していくので、最後までお付き合いください。

●Kotlinのプロパティとは

Kotlinのプロパティは、Javaなどの他のプログラミング言語でいうところのフィールドに相当します。

しかし、Kotlinでは、フィールドに直接アクセスするのではなく、プロパティを通じてアクセスするため、コードがより簡潔で可読性が高まります。

○プロパティの基本

Kotlinのプロパティは、クラスのインスタンス変数のようなものです。

プロパティは、値を保持するフィールドと、その値にアクセスするためのgetter、setterメソッドから構成されます。

getterはプロパティの値を取得するためのメソッド、setterはプロパティの値を設定するためのメソッドです。

プロパティは、valキーワードを使って読み取り専用に、varキーワードを使って読み書き可能に定義できます。

読み取り専用のプロパティは、getterメソッドのみを持ち、値の変更はできません。読み書き可能なプロパティは、getterとsetterメソッドの両方を持ちます。

○プロパティの特徴とメリット

Kotlinのプロパティの最大の特徴は、そのシンタックスの簡潔さと直感性です。

Javaでは、フィールドにアクセスするためには、明示的にgetterやsetterメソッドを定義しなければなりませんが、Kotlinではこれが必要ありません。

プロパティ名を直接使ってアクセスでき、内部的には適切なgetterやsetterメソッドが呼び出されます。

また、Kotlinのプロパティには、デフォルトのgetterとsetterが自動で提供され、カスタムのgetterやsetterも簡単に定義できます。

これにより、フィールドの値にアクセスする際の制御を柔軟に行うことができます。

●Kotlinのプロパティの使い方

Kotlinのプロパティを使うことで、簡潔なコードを書くことができ、Javaと比べて多くの冗長なコードを排除することができます。

ここでは、Kotlinのプロパティの基本的な使い方から、GetterとSetterの活用方法までを具体的なサンプルコードとともに解説していきます。

○サンプルコード1:基本的なプロパティの定義

プロパティは変数のように振る舞いますが、裏側ではgetterやsetterが動作しています。

ここではKotlinでのプロパティの基本的な定義方法を紹介します。

class User {
    var name: String = "Taro"
}

このコードでは、Userクラスにnameというプロパティを定義しています。

この例では、nameを”Taro”という文字列で初期化しています。

このプロパティにアクセスするとき、特別なメソッドを書かなくても、次のように直接アクセスできます。

val user = User()
println(user.name)  // Taro

○サンプルコード2:GetterとSetterの活用

Kotlinでは、プロパティの値の取得や設定の際に独自のロジックを追加することができます。

それがGetterとSetterです。以下のコードは、プロパティにGetterとSetterを追加した例です。

class User {
    var name: String = "Taro"
        get() = field.capitalize()
        set(value) {
            field = value.toLowerCase()
        }
}

このコードでは、nameのGetterでは取得する文字列を大文字始まりに、Setterでは小文字に変換しています。

fieldはプロパティのバックフィールドを参照するキーワードです。

このプロパティを使うと次のように動作します。

val user = User()
println(user.name)  // 出力: Taro

user.name = "JIRO"
println(user.name)  // 出力: Jiro

まず、nameの初期値として設定されている”Taro”が、Getterによって大文字始まりの”Taro”として取得されます。

次に、”JIRO”という文字列をSetterを使って設定すると、この文字列は小文字の”jiro”に変換されて保存されます。

その後、Getterを使って取得すると、”Jiro”という大文字始まりの文字列が取得されるという流れになります。

○サンプルコード3:プロパティの初期化

Kotlinのプロパティは、その宣言時に初期値を与えることができます。

しかし、場合によっては初期化を遅らせたい場面もあるかと思います。

ここでは、プロパティの初期化方法について、具体的なコードを元に詳しく解説していきます。

まず、最も基本的なプロパティの初期化方法を確認しましょう。

class Person {
    var age: Int = 20
}

このコードでは、Personクラス内にageというプロパティを定義し、その初期値として20を設定しています。

このように、プロパティ宣言時に直接初期値を与える方法は、非常にわかりやすく、Kotlinでよく使われる初期化方法です。

しかし、初期化には条件をもたせたい場合もあるでしょう。

例えば、外部からの入力値に応じて初期値を設定したい場合などです。

そのような場合には、コンストラクタを利用して初期化を行います。

class Person(ageValue: Int) {
    var age: Int = ageValue
}

このコードは、コンストラクタの引数ageValueを受け取り、それをageプロパティの初期値として設定しています。

このように、外部からの入力値を直接プロパティの初期値として設定することも可能です。

○サンプルコード4:LateinitとDelegated Properties

プロパティの初期化には、遅延初期化というテクニックもあります。

これは、プロパティの初期値の設定を遅らせ、実際に使用されるタイミングで初めて値を設定する方法です。

この方法を実現するためのキーワードがlateinitです。

class Database {
    lateinit var connection: Connection
    fun connect() {
        connection = Connection()  // 実際に使用するタイミングで初期化
    }
}

lateinitキーワードを使用することで、connectionプロパティの初期化をconnectメソッドの呼び出し時まで遅延させることができます。

一方、Delegated Propertiesは、プロパティの振る舞いを他のオブジェクトに委譲するテクニックです。

これを使うことで、プロパティの取得や設定のロジックを再利用することができます。

var value: String by DelegateClass()

このコードでは、DelegateClassというクラスが、valueプロパティの取得や設定のロジックを担当しています。

このように、Delegated Propertiesを使用することで、コードの再利用性を高めることができます。

●Kotlinのプロパティの応用例

Kotlinのプロパティは基本的な使い方だけでなく、多岐にわたる応用例を持っています。

特に初心者の方が効果的にプロパティを利用するためのテクニックをいくつか紹介します。

○サンプルコード5:カスタムGetterの実装

プロパティの値を取得する際の振る舞いをカスタマイズすることができます。

この振る舞いを変更することで、計算された値や特定の条件下での値を返すことが可能になります。

class Rectangle {
    var width: Int = 0
    var height: Int = 0
    val area: Int
        get() = width * height
}

このコードでは、Rectangleクラス内にwidthheightというプロパティが定義されています。

そして、areaというプロパティの値は、widthheightの乗算結果として得られます。

このようにカスタムGetterを使用することで、プロパティの取得時の振る舞いをカスタマイズできます。

このコードを利用して、widthを5、heightを10とした場合、areaプロパティは50として計算されます。

○サンプルコード6:カスタムSetterの制約付き

一方で、プロパティの値を設定する際の振る舞いもカスタマイズすることができます。

特定の条件下での値の設定や、制約を持った値の設定などが可能です。

class Age {
    var value: Int = 0
        set(v) {
            if (v in 0..150) {
                field = v
            } else {
                println("不正な年齢が設定されました。")
            }
        }
}

このコードでは、Ageクラス内にvalueというプロパティが定義されています。

このプロパティの値を設定する際、0から150の範囲外の値が設定されると、警告メッセージが表示されます。

このようにカスタムSetterを使用することで、プロパティの設定時の振る舞いをカスタマイズできます。

このコードを利用して、valueに200を設定しようとすると、「不正な年齢が設定されました。」というメッセージが表示され、valueの値は変更されません。

○サンプルコード7:Lazy propertyの利用

Kotlinには、プロパティの値が最初にアクセスされた際に計算されるlazyデリゲートがあります。

これは、リソースを節約するためや、初期化に時間がかかるような処理を遅延させる際に有効です。

計算された値はキャッシュされ、次回からは再計算されません。

class Data {
    val heavyData: String by lazy {
        println("heavyDataを初期化しています…")
        "これは重たいデータです"
    }
}

上のコードでは、heavyDataというプロパティがlazyデリゲートを使って初期化されます。

このプロパティに初めてアクセスする際、コンソールに”heavyDataを初期化しています…”と表示され、文字列”これは重たいデータです”が代入されます。

例えば、次のようにDataクラスのインスタンスを作成し、heavyDataにアクセスすると上記の処理が実行されます。

val data = Data()
println(data.heavyData)  // ここでheavyDataが初めてアクセスされる

上記のコードを実行すると、コンソールには”heavyDataを初期化しています…”と表示された後、”これは重たいデータです”と表示されます。

○サンプルコード8:Observable propertyの活用

Kotlinでは、プロパティの値が変更されるたびに通知を受け取ることができるobservableデリゲートも提供されています。

これにより、特定のプロパティの変更を監視し、変更があった際の動作をカスタマイズすることが可能です。

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("初期値") { _, old, new ->
        println("nameが$oldから$newに変更されました。")
    }
}

このコードでは、Userクラス内にnameというプロパティが定義されており、このプロパティの値が変更されるたびにコンソールにメッセージが表示されます。

例えば、次のようにUserクラスのインスタンスを作成し、nameプロパティの値を変更すると、変更を検知してメッセージが表示されます。

val user = User()
user.name = "Taro"

上記のコードを実行すると、コンソールには”nameが初期値からTaroに変更されました。”と表示されます。

○サンプルコード9:Map-backed propertiesの実装

Kotlinでは、プロパティをMapでバックアップすることができる特徴があります。

これにより、動的なプロパティセットを持つオブジェクトを効率的に実装できます。

特にJSONや設定ファイルのような動的なデータ構造をモデル化する際に役立ちます。

考え方としては、実際のデータはMapに格納され、プロパティへのアクセスや代入はこのMapを介して行われることになります。

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

val userMap = mapOf(
    "name" to "Taro",
    "age"  to 25
)

val user = User(userMap)

このコードでは、Userクラスのプロパティnameageは、コンストラクタで渡されたmapをバックアップとして使用しています。

このため、Userインスタンスを作成する際には、適切なキーを持つMapを渡す必要があります。

上記の例では、userMapというMapを作成し、その後Userクラスのインスタンスを作成しています。

この際、user.nameuser.ageにアクセスすると、内部的にはuserMapから値を取得しています。

○サンプルコード10:拡張プロパティの作成

Kotlinでは、既存のクラスに新しいプロパティを追加することなく、拡張プロパティを定義することができます。

この特徴を使用すると、既存のライブラリやフレームワークのクラスを改変せずに新しい機能を追加することが可能となります。

例えば、Stringクラスに新しいプロパティを追加してみましょう。

val String.numVowels
    get() = count { it in "aeiouAEIOU" }

val sampleText = "Hello, Kotlin!"
println("母音の数: ${sampleText.numVowels}")

上記のコードでは、StringクラスにnumVowelsという拡張プロパティを追加しています。

このプロパティを使用すると、文字列内の母音の数を取得できます。

このコードを実行すると、sampleTextに含まれる母音の数が計算され、「母音の数: 4」と表示されます。

●注意点と対処法

Kotlinでのプロパティの利用には多くの利点がありますが、その一方で知っておくべき注意点や落とし穴も存在します。

ここでは、そのような注意点や、それらの問題を回避するための対処法について詳しく解説します。

○プロパティの初期化に関する注意点

Kotlinでは、非null型のプロパティは宣言時かコンストラクタ内で必ず初期化する必要があります。

初期化しない場合、コンパイルエラーとなります。

class Person {
    var name: String  // エラー: 初期化が必要
    var age: Int = 0 // 正常: 初期値が設定されている
}

このコードでは、nameプロパティが初期化されていないため、エラーとなります。

○MutableとImmutableの違いと注意点

Kotlinではvarvalでプロパティを宣言できますが、これらはそれぞれMutable(変更可能)とImmutable(変更不可)を表しています。

class Sample {
    var mutableProperty: Int = 0       // Mutable: 値を変更可能
    val immutableProperty: String = "Immutable" // Immutable: 値を変更不可
}

上記のコードでmutablePropertyは後から値を変更できるのに対し、immutablePropertyは一度設定したら変更できないことを意味します。

○サンプルコード11:初期化の遅延と注意点

Kotlinでは、プロパティの初期化を遅延させるlateinitキーワードが提供されています。

しかし、このlateinitを使用する際は、プロパティが初期化される前にアクセスされると実行時エラーになる点に注意が必要です。

class Demo {
    lateinit var lateInitProperty: String

    fun initProperty() {
        lateInitProperty = "Initialized"
    }
}

val demo = Demo()
// demo.lateInitProperty  // ここでアクセスするとエラー
demo.initProperty()
println(demo.lateInitProperty)  // "Initialized" と出力される

このコードではlateInitPropertyinitPropertyメソッド内で初期化されます。

しかし、それ以前にアクセスを試みると、エラーが発生します。

○サンプルコード12:VarとValの使い分け

varvalの適切な使い分けは、Kotlinプログラミングの基本です。

一般的には、変更する必要がないプロパティや変数はvalで宣言することが推奨されています。

fun sampleFunction() {
    val fixedValue = "Fixed"
    var variableValue = "Variable"

    // fixedValue = "Changed"  // エラー: valは再代入不可
    variableValue = "Changed"  // 正常: varは再代入可能
}

上記のコードでは、fixedValueは再代入しようとするとエラーが発生しますが、variableValueは再代入が可能です。

変更の可能性がある場合はvarを、変更しない場合はvalを選択すると良いでしょう。

●カスタマイズ方法

Kotlinのプロパティは非常に柔軟で、多様なカスタマイズが可能です。

ここでは、プロパティのカスタマイズ方法をいくつかのサンプルコードとともに解説します。

○サンプルコード13:カスタムGetterのカスタマイズ例

Kotlinでは、プロパティのgetterをカスタマイズすることができます。

例えば、特定の条件下で異なる値を返すようなgetterを実装することが可能です。

class Temperature(val celsius: Double) {
    val fahrenheit: Double
        get() = celsius * 9/5 + 32
}

val temp = Temperature(25.0)
println(temp.fahrenheit)  // 77.0 と出力される

このコードでは、摂氏を渡して華氏を取得するプロパティをカスタムgetterとして実装しています。

Temperature(25.0)で摂氏25度を指定すると、fahrenheitプロパティで華氏77度を取得できます。

○サンプルコード14:Delegated Propertiesのカスタマイズ

KotlinのDelegated Propertiesは、プロパティの値の取得や設定のロジックを別のオブジェクトに委譲する機能です。

これにより、再利用性やモジュール性を高めることができます。

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") { _, old, new ->
        println("$old -> $new")
    }
}

val user = User()
user.name = "Alice"  // <no name> -> Alice と出力される
user.name = "Bob"    // Alice -> Bob と出力される

このコードでは、nameプロパティの値が変わるたびに、変更前と変更後の値を出力するカスタムロジックを実装しています。

Delegates.observableを使うことで、このようなカスタムな振る舞いを簡単に追加することができます。

○サンプルコード15:Property delegateのカスタマイズ

Kotlinでは、プロパティの動作をカスタマイズするための仕組みとして、プロパティデリゲートを提供しています。

これは、プロパティのgetterやsetterの動作を別のオブジェクトに委譲することで、再利用可能なロジックを構築することができます。

たとえば、特定の条件を満たす場合にのみプロパティの値を変更させたい場合、カスタムデリゲートを作成してその条件を実装することができます。

ここでは、0以上の整数のみを設定することができるプロパティのデリゲートのサンプルコードを紹介します。

import kotlin.reflect.KProperty

class PositiveIntDelegate() {
    private var value: Int = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value >= 0) {
            this.value = value
        }
    }
}

class SampleClass {
    var positiveInt: Int by PositiveIntDelegate()
}

val sample = SampleClass()
sample.positiveInt = 5
println(sample.positiveInt)  // 5と出力される

sample.positiveInt = -3
println(sample.positiveInt)  // 5のまま変わらず出力される

このコードでは、PositiveIntDelegateというカスタムデリゲートを定義しています。

このデリゲートを使用すると、プロパティの値として0以上の整数のみ設定することができます。

0未満の値を設定しようとすると、何も起こらず以前の値が保持されます。

○サンプルコード16:プロパティの拡張機能の追加

Kotlinでは、プロパティの拡張機能を追加することも可能です。

これにより、既存の型に新しいプロパティを追加することができます。

ここでは、String型にisNumericという拡張プロパティを追加するサンプルコードを紹介します。

val String.isNumeric: Boolean
    get() = this.matches(Regex("\\d+"))

val numberString = "12345"
println(numberString.isNumeric)  // trueと出力される

val nonNumberString = "abcde"
println(nonNumberString.isNumeric)  // falseと出力される

このコードでは、文字列が数字のみで構成されているかを確認するisNumericという拡張プロパティを定義しています。

このプロパティを使用すると、任意の文字列が数字のみで構成されているかを簡単に確認することができます。

●プロパティの高度な活用

Kotlinでは、初心者から上級者まで、さまざまなレベルのプロパティの使い方が提供されています。

このセクションでは、特に上級者向けのプロパティの高度な活用方法を中心に解説していきます。

○サンプルコード17:プロパティの継承とオーバーライド

クラスの継承において、基底クラスで定義されたプロパティをサブクラスでオーバーライドすることができます。

これにより、特定のプロパティの動作をサブクラスごとにカスタマイズすることが可能となります。

下記のサンプルコードは、PersonクラスのnameプロパティをStudentクラスでオーバーライドしています。

open class Person(open val name: String)

class Student(override val name: String, val studentId: Int) : Person(name)

val student = Student("田中太郎", 12345)
println(student.name)  // 田中太郎と出力される

このコードでは、Personクラスに定義されたnameプロパティを、Studentクラスでオーバーライドして使用しています。

こうすることで、サブクラス固有のプロパティを持つと同時に、基底クラスのプロパティも利用することができます。

○サンプルコード18:プロパティの拡張とジェネリクス

Kotlinでは、ジェネリクスを使用して型をパラメータ化することができます。

これにより、より汎用的なコードを書くことが可能となります。

プロパティの拡張にジェネリクスを組み合わせることで、さまざまな型に対して共通のプロパティを追加することができます。

ここでは、List<T>lastIndexという拡張プロパティを追加するサンプルコードを紹介します。

val <T> List<T>.lastIndex: Int
    get() = this.size - 1

val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.lastIndex)  // 4と出力される

val chars = listOf('a', 'b', 'c')
println(chars.lastIndex)    // 2と出力される

このコードでは、どんな型のリストでも、最後の要素のインデックスを取得するlastIndexという拡張プロパティを追加しています。

ジェネリクスを使用することで、特定の型に依存しない汎用的なプロパティを定義することができます。

○サンプルコード19:プロパティのインライン化の活用

Kotlinでは、inlineキーワードを使用して、特定のプロパティや関数をインライン化することができます。

インライン化とは、呼び出し元に直接コードが挿入されるように変換されることを指します。

これにより、ランタイムでのオーバーヘッドを減少させ、パフォーマンスの向上を図ることができます。

特に、ラムダや高階関数の引数として使用される場合に効果的です。

class Rectangle {
    var height: Double = 0.0
    var width: Double = 0.0

    inline val area: Double
        get() = height * width
}

fun main() {
    val rectangle = Rectangle()
    rectangle.height = 5.0
    rectangle.width = 10.0
    println("矩形の面積は${rectangle.area}です。")  // 矩形の面積は50.0です。と出力される
}

このコードでは、矩形のheightwidthを元に、矩形の面積を計算するareaというインラインプロパティを定義しています。

このareaプロパティが呼び出される際、コンパイル時に計算ロジックが直接呼び出し元のコードに埋め込まれます。

○サンプルコード20:プロパティのアノテーションとリフレクション

Kotlinでは、アノテーションを使用して、プロパティや関数にメタデータを追加することができます。

このメタデータはリフレクションという技術を使って、ランタイムで取得や操作を行うことができます。

@Target(AnnotationTarget.PROPERTY)
annotation class CustomAnnotation(val description: String)

class MyClass {
    @CustomAnnotation("これはサンプルのプロパティです。")
    val sampleProperty: String = "サンプル"
}

fun main() {
    val myClass = MyClass()
    val prop = MyClass::sampleProperty
    val annotations = prop.annotations
    for (annotation in annotations) {
        if (annotation is CustomAnnotation) {
            println("アノテーションの説明: ${annotation.description}")  // アノテーションの説明: これはサンプルのプロパティです。と出力される
        }
    }
}

このコードで、CustomAnnotationという独自のアノテーションを定義し、それをMyClass内のsamplePropertyに適用しています。

その後、MyClass::samplePropertyを使用して、プロパティのアノテーションリストを取得し、その中からCustomAnnotationを取り出してその説明を表示しています。

まとめ

Kotlinのプロパティには多くの特徴と活用法があります。

今回の記事では、プロパティの基本的な使い方から、高度な活用法、注意点、カスタマイズ方法に至るまでを詳細に解説しました。

サンプルコードを通じて、実際のコーディング時にどのようにプロパティを扱うべきかの理解を深めることができたことを願っています。

Kotlinは現代のアプリケーション開発において欠かせない言語となっています。

その特性や機能を正確に理解し、効果的に活用することで、より品質の高いコードを書くことができます。

特にプロパティは、Kotlinの強力な特性の1つとして、その効果的な活用が求められます。

今回の記事が、Kotlinを学んでいる初心者の方々にとって、プロパティの理解と活用の手助けとなることを期待しています。

今後のコーディングの際に、今回の内容を参考にしながら、Kotlinのプロパティを最大限に活用してみてください。