0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】Kotlinの `lazy` 完全解説

Last updated at Posted at 2025-10-13

はじめに

Kotlinでは「値をすぐに計算せず、必要になった時にだけ計算する」ことができます。
それを実現するのが lazy

つまり、

「最初にアクセスされた瞬間に初期化される val
です。

メモリ効率や初期化順序を最適化できるため、
Android・サーバー・テストなど、あらゆる場面で活用されます。


基本構文

val value by lazy { initializer }
  • by lazy { ... } の中に初期化処理を書く
  • 初めてアクセスされた時だけ、その処理が実行される
  • 2回目以降はキャッシュされた値を返す

val userName by lazy {
    println("Initializing userName...")
    "Anna"
}

fun main() {
    println("Before access")
    println(userName)
    println(userName)
}

出力:

Before access
Initializing userName...
Anna
Anna

⚙️ 初期化は1回だけ。2回目以降はキャッシュ済み。


lateinit との違い

比較項目 lateinit lazy
修飾対象 var val
初期化タイミング 手動で代入した時 最初にアクセスされた時
null許容型 ❌ 不可 ✅ 可
スレッドセーフ ❌ デフォルト非対応 ✅ デフォルトで対応
例外発生タイミング 未初期化アクセス時(実行時) 初期化処理で例外が起きた時のみ
主な用途 DI・View・テスト キャッシュ・重い初期化処理

lazy の動作モード

Kotlinの lazy() 関数には3種類の動作モードがあります。

SYNCHRONIZED(デフォルト)

複数スレッドから同時アクセスされても一度だけ初期化。

val config by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    loadConfig()
}

PUBLICATION

複数スレッドが同時に初期化しても、一番最後の値だけ保持。

val data by lazy(LazyThreadSafetyMode.PUBLICATION) {
    fetchData()
}

NONE

スレッドセーフではないが最も高速。

val cache by lazy(LazyThreadSafetyMode.NONE) {
    createCache()
}

Androidなどシングルスレッド環境では NONE が高速で有効。


lazy の内部構造

lazy の戻り値は Lazy<T> インターフェース。

public interface Lazy<out T> {
    val value: T
    fun isInitialized(): Boolean
}

by lazy は実は Lazy<T>委譲プロパティ(Property Delegation)です。

val foo by lazy { 42 }

// 実際には以下のように展開される
private val _foo = lazy { 42 }
val foo get() = _foo.value

Kotlinの by は「プロパティ委譲構文」。
lazy はその代表的な実装のひとつ。


lazy の状態を確認する

val result by lazy { compute() }

println(result.isInitialized()) // ❌ エラー(直接呼べない)

実は lazy 自体を参照して確認する必要があります。

val resultLazy = lazy { compute() }
val result by resultLazy

println(resultLazy.isInitialized()) // false
println(result)                     // compute() 実行
println(resultLazy.isInitialized()) // true

実践例1:重い初期化を遅らせる

class DataRepository {
    val database by lazy { Database.connect() }
}

データベース接続はコストが高いため、必要になるまで初期化しない設計。


実践例2:Android Activity

class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
    }
}

lateinit ではなく lazy を使うと、安全に遅延初期化+valで不変にできる。


実践例3:キャッシュ利用

val config by lazy {
    println("Loading config...")
    loadConfigFile()
}

fun loadConfigFile(): Map<String, String> {
    // 重いファイルIOをシミュレート
    Thread.sleep(1000)
    return mapOf("theme" to "dark", "lang" to "ja")
}

fun main() {
    println(config["theme"]) // 初回:読み込み発生
    println(config["lang"])  // 2回目:キャッシュ利用
}

注意点とアンチパターン

パターン 問題点
lazy で重すぎる処理をUIスレッドで実行 初回アクセス時にラグが発生
lazy 内で例外が発生 再アクセスしても再初期化されない
lateinit と混同して使用 目的が異なる(val vs var
キャッシュとして長期保持 メモリリークの原因になる場合あり

まとめ

ポイント 説明
lazy は「最初にアクセスされた時だけ初期化」
デフォルトはスレッドセーフ(SYNCHRONIZED
by lazy は委譲プロパティ構文を利用
重い処理・設定・View初期化などに最適
Androidでは lateinit より lazy + val が安全なケース多数

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?