
みなさまこんにちは〜!
メモリアインクのすだです。
今回は、Kotlinでの開発における遅延初期化について、その必要性と使い方をわかりやすく解説していきます。
この記事を読んでわかること…
・遅延初期化とは?
・lateinit var の使い方
・by lazy の使い方
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
遅延初期化とは?
遅延初期化とは、「プロパティの初期化を、宣言と同時にではなく、必要なタイミングまで“あとに回す”こと」です。
Kotlinはnull安全を重視している言語なので、
「値がまだ設定されてない状態で使われる」ことを避けたい。
そのためval
や var
のプロパティは、
以下のように宣言と同時に初期化(= 最初の値を代入すること)が必要です▼
val name: String = "Taro" // OK
val age: Int // エラー!初期化されていない
しかし、「このプロパティは、最初でなくあとで初期化したい」というタイミングがいくつかあります。
たとえば以下のような場合です▼
- Activityの
onCreate()
内で初期化したい- (→画面が生成されたタイミングでしか使えない値や部品。
findViewById()
で取得する UI 部品など)
- (→画面が生成されたタイミングでしか使えない値や部品。
- 最初に使われたときにだけ計算したい
- (→宣言時に初期化した場合、常に起動時に処理が実行されてしまうため、
初回アクセス時にだけ重たい処理を行って、あとは結果を使い回したい。)
- (→宣言時に初期化した場合、常に起動時に処理が実行されてしまうため、
- 初期値が非nullだけど今すぐ決められない
→このようなときに使えるのが「遅延初期化」です。
Kotlinにおける遅延初期化の方法
遅延初期化を行う方法は、以下の2種類があります。
lateinit | あとから代入することを前提にした var (再代入可能な変数) に使える |
by lazy | 初めて使われたときに初期化する val (再代入不可能な変数) に使える |
lateinit
lateinitは以下の特徴があります。
- var(変更可能)のみ使える
- 非null型(String など)のみ使える
- Int, Boolean など プリミティブ型には使えない
- 初期化前にアクセスするとエラーになる(UninitializedPropertyAccessException)
class MainActivity : AppCompatActivity() {
// lateinit を使って後から初期化するTextView
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ここで初期化
textView = findViewById(R.id.myTextView)
// 初期化したTextViewを使う
textView.text = "こんにちは!"
}
}
このように、
「nullにしたくないけど、あとで初期化したい」といったようなプロパティの宣言に使えるのがlateinitです。
isInitializedで初期化済みか調べる
Kotlinでは、lateinit で宣言されたプロパティがすでに初期化されているかどうかを判定するために、isInitialized
を使用することができます。
if (::プロパティ名.isInitialized) {
// 初期化済みの場合の処理
}
by lazy
by lazyは以下の特徴があります。
- val に使う(読み取り専用)
- 初めて使われたときだけ初期化される
- それ以降はキャッシュされる(再計算されない)
- スレッドセーフ(複数スレッドでも安全)
class MainActivity : AppCompatActivity() {
// 初回アクセス時にだけ初期化されるユーザー情報
private val currentUser: User by lazy {
User(id = 1, name = "Hanako")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// この時点で初めて currentUser にアクセス → lazyが実行される
println("ようこそ ${currentUser.name} さん!")
// もう一度アクセスしても、再計算されずキャッシュされた値が使われる
greetUser()
}
private fun greetUser() {
Toast.makeText(this, "こんにちは、${currentUser.name} さん", Toast.LENGTH_SHORT).show()
}
}
このように、
「アクセスされるまでは何もしないけど、初回アクセス時にだけ処理が行われて、以降は使い回される」といったようなプロパティの宣言に使えるのがlateinitです。
実践的な実装例
シンプルなAdapterとリスナー注入パターン
たとえば、リストの中のアイテムをタップしたときに「Activity側で処理したい」とします。
そのとき、「Adapter内でリスナーを用意してあとからActivity側から渡す」ことで、
画面によってクリック動作を変えたいときに便利になります。
Adapter:
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyViewHolder>() {
private lateinit var listener: OnItemClickListener
// 外部からリスナーをセットする関数
fun setOnItemClickListener(itemClickListener: OnItemClickListener) {
listener = itemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(android.R.layout.simple_list_item_1, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = items[position]
holder.textView.text = item
holder.itemView.setOnClickListener {
if (::listener.isInitialized) {
listener.onItemClick(item)
}
}
}
override fun getItemCount(): Int = items.size
}
interface OnItemClickListener {
fun onItemClick(item: String)
}
呼び出し側:
val adapter = MyAdapter(listOf("A", "B", "C"))
adapter.setOnItemClickListener(object : OnItemClickListener {
override fun onItemClick(item: String) {
Toast.makeText(this@MainActivity, "$item がクリックされました", Toast.LENGTH_SHORT).show()
}
})
recyclerView.adapter = adapter
ViewModelの初期化
ViewModel は画面が再生成されるたびに必要だが、初回アクセス時にだけ作ればOKなので、
by lazy の使用が適しています。
class MainActivity : AppCompatActivity() {
// ViewModel を遅延初期化(初回アクセス時に1回だけ生成)
private val viewModel by lazy {
ViewModelProvider(this)[MainViewModel::class.java]
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ↓ 初回アクセス時にViewModelが初期化される
viewModel.loadData()
viewModel.message.observe(this) { message ->
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
}
まとめ
おつかれさまでした。いかがでしたでしょうか!
Kotlinの遅延初期化は、「初期化の自由度を高める」「nullを使わず安全に扱う」という目的に非常に役立ちます。
UIのライフサイクルや非同期処理と組み合わせて、状況に合った方法を選びましょう。



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント