[毎日Kotlin] Day35. Lazy property

はじめに

毎日Kotlinシリーズです。

このシリーズを初めての方はこちらです。「毎日Kotlin」はじめました | Developers.IO

問題

Lazy property | Try Kotlin

Add a custom getter to make the 'lazy' val really lazy. It should be initialized by the invocation of 'initializer()' at the moment of the first access.

You can add as many additional properties as you need.

Do not use delegated properties!

class LazyProperty(val initializer: () -> Int) {
    
    val lazy: Int
        get() {
            TODO()
        }
}

狙い

ここで考えて欲しい問題の意図はなんだろうか?

たまにプラットフォームの仕様で、クラス初期化時にタイミング的に初期化できないことがあります。例えば。Androidだったら、onCreateで初期化しないといけないことが多くあります。そのときは、遅延初期化をすることでvar or Nullableにすることなく、valの状態で初期化出来るのでうれしいです。

解答例

class LazyProperty(val initializer: () -> Int) {
    var value: Int? = null
    val lazy: Int
        get() {
            if (value == null) {
                value = initializer()
            }
            return value!!
        }
}

こちらの解答は自前で遅延初期化を実装したときの場合です。

すでにスタンダードライブラリーにあるのでそちらを使用しましょう。遅延初期化するの方法としてよく使われる3つの方法があります。

    val value: Int by lazy {}
    lateinit var value: Int
    var value: Int? = null

lazyはvalueに初めてアクセスしたとき、lazy{}の処理が呼ばれ初期化します。以降アクセスするときは、初期化処理は呼ばれず、前回の値を返します。

使用例

   val binding by lazy {
        DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
    }

lateinit varは、getの前に、setをしなければならない。初期化(set)して、その後getの順番であれば使用可能です、valではなくvarなので何度でも上書きはできます。間違って初期化してないのにgetした場合は、例外が投げられます。

使用例

class ChildFragment : Fragment(){
    private lateinit var binding: FragmentChildBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate<FragmentChildBinding>(inflater, R.layout.fragment_child, container, false)
   }
}

var value: Int? =nullは、とりあえず、Nullableで定義してしまえば問題ありません。これのデメリットは、本来はNullじゃないのにNullableにしてるので、いちいちNullチェックが必要になったり、本来Nullじゃないものなので、バグかなにかでNullが入ってきた時に落ちてくれないので、バグに気づきにくくなったりします。

なるべく最初の2つを用いて遅延初期化しましょう。

補足

以前、Androidでの使い分けを図にしましたので、ぜひこちらも参照。

あとがき

Day36.でまたお会いしましょう。