Effective Kotlin - プロパティは動作ではなく状態を表すべき

KotlinのプロパティはJavaのフィールドと似て見えますが、異なる概念を持っています。


//Kotlinproperty
var name:String?=null

//Javafield
String name=null;

同じ方法で使用するように見えても、Kotlinのプロパティはより多くのことができます。

プロパティ宣言の完全な構文


var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

カスタムGet、Set ( var )


var name: String? = null
    get() = field?.toUpperCase()
    set(value) { if(!value.isNullOrBlank()) { field = value }

カスタムGet ( val )


val fullName: String?
    get() = "$name $surname"

バッキングフィールド

フィールドをカスタマイズして宣言することはできません。ただし、プロパティにバッキングフィールドが必要な場合、自動的に生成されます。このバッキングフィールドは、アクセサ内で field 識別子を使用して参照できます。


var counter = 0 // 初期化子はバッキングフィールドを直接割り当てます
    set(value) {
        if (value >= 0)
            field = value
            // counter = value // エラー StackOverflow: 実際の名前 'counter' を使用するとセッターが再帰的になります
    }

関数との違いは?

Kotlinでのプロパティはそのオブジェクトの状態を意味するのに対し、関数はそのオブジェクトの動作を意味します。


// プロパティ
val isEmpty: Boolean
  get() = amount == 0


// 関数
fun isEmpty(): Boolean {
  return amount == 0
}

考えてみましょう。

常にプロパティを利用するほうが良いでしょうか?


class FruitBucket(
    val price: Int,
    val discount: Int,
    var amount: Int
) {
  val isEmpty: Boolean
    get() = amount == 0
  val salePrice: Int
    get() = price - discount
}

上記のコードを改善する場合、次のようになります。
アクセスするたびに毎回演算を行うのは非効率的だからです。


class FruitBucket(
    val price: Int,
    val discount: Int,
    var amount: Int
) {
  val isEmpty: Boolean
    get() = amount == 0
  val salePrice: Int = price - discount
}

データを保持せずにそのプロパティを使用できます。

何らかの理由でDate型を直接使用できない場合でも、Kotlinのプロパティ機能を使用して、Date型のデータを保存するのではなく、他の値を利用してDate型の値を使用できます。(シリアル化などを行う場合、millisの値だけを保持しておき、使用時にDate形式に変換する形態)


var date: Date
    get() = Date(millis)
    set(value) {
        millis = value.time
    }

プロパティは単純なフィールドではなく、アクセサとしても使用できます。


val Context.preferences:SharedPreferences
    get() = PreferenceManager.getDefaultSharedPreferences(this)
val Context.inflater:LayoutInflater
    get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val Context.notificationManager:NotificationManager
    get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

プロパティ機能は万能でしょうか?

ループ、繰り返しなどの特定のロジックが含まれている、またはアルゴリズム的な部分 が注入されたプロパティは正しい使用法ではありません。


// このようなことはしないでください!
val Tree<Int>.sum:Int
    get() = when (this) {
        is Leaf -> value
        is Node -> left.sum + right.sum
    }

プロパティは通常、状態を表したり設定したりするためにのみ使用する必要があります。
プロパティを使用するよりも関数を使用することを推奨する例を挙げます。

  • O(1) よりも計算量が大きい場合(計算コストが高い場合)
  • 単純作業(ログ出力、要素の更新など)以上のビジネスロジックが含まれる場合
  • メンバーを2回連続で呼び出した際に、異なる結果が出力される場合
  • Int.toDouble() のように慣用的な表現の重複が生じる場合
  • Getter内でプロパティの状態を変更する場合

参考資料

AD