プロパティの初期化を遅らせる方法
Android 開発ではプロパティの初期化を遅らせたい場面がけっこうあります。
例えば、以下のようなケース。
class MainActivity : AppCompatActivity() {
// この時点では view が生成されていないので view が取れない。
// とりあえず null を代入...
private var textView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ここで初めて view が取れる。
textView = findViewById(R.id.text_view)
}
//...
}
Kotlin でプロパティの初期化を遅らせる方法として lateinit
と by lazy
がよく使われると思うのですが、機能が似ているのでどのように使い分ければいいのか悩むときがあります。
lateinit
の特徴
lateinit
には以下のような特徴があります。
- int などのプリミティブ型は指定できない
- var で宣言する必要がある
- 必ず non-null となる
先ほどの例を lateinit
で書くと次のようになります。
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ここで初めて初期化できる。
textView = findViewById(R.id.text_view)
}
//...
}
最初の例ではプロパティ textView を null で初期化する必要がありましたが、lateinit を使うことで nun-null として扱うことができるようになりました。
ただし、var で宣言する必要があるため、別の値が再代入されてしまう可能性があるのが注意点です。
by lazy
次に by lazy
の特徴を見てみます。
- 型に制限なし
- val で宣言する必要がある
- nullable、non-null どちらも可能
- 対象のプロパティが初めて呼び出されたときに初期化される
- 初期化されたら次回以降は必ず同じ値を返す
class MainActivity : AppCompatActivity() {
// nullable でも OK
private val userData: User? by lazy { fetchUserData() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 初めて呼び出されるタイミングで初期化される。
userData?.let { showUserDate(it) }
}
//...
}
by lazy
は一度初期化されると値はキャッシュされ、以降必ず同じ値を返します。そのため、ビューを by lazy
で初期化してしまうと、ビューに変更を加えようとしても反映してくれません。ビューを扱う場合は lateinit
を使う方がいいでしょう。
逆に by lazy
は nullable なプロパティも扱うことができるため、null が入る可能性がある場合は by lazy
を使うべきかと思います。
どのように使い分けるべきか
どちらでも OK な場合や、「プリミティブ型だけど、後から変更を加えたい」といったどちらにも当てはまらない場合もあるかと思いますので、それぞれの状況に合わせて使い分けてみてください。
※ 「プリミティブ型だけど、後から変更を加えたい」 場合には by Delegates.notNull
が選択肢になるかなと思います。
lateinit
が良さげな場面
- 初期化するタイミングを指定したい場合
- 型が View の場合
- 後から変更を加えたい場合
by lazy
が良さげな場面
- 初期化するタイミングにこだわりがない場合
- 型がプリミティブの場合
- 後から変更されたくない場合
- nullable な場合
- 必ず使われるとは限らない場合(使われない場合は初期化されない)
ちなみに
これは個人的な感想になりますが、どちらでも OK な場合は可読性が高そうな by lazy
を使うようにしています。
by lazy
はプロパティの宣言と初期化処理を一体化させたハッピーセット🍔 なので、わざわざ初期化処理を確認しに行く必要がありません。
本来 onCreate()
などに記述する初期化処理が減ることで、その他の重要なロジックに集中することができるのも嬉しいポイントです。