Java(Kotlin/JVM)のラムダ式は必ず別インスタンスとなるわけではない

Javaの匿名インナークラスの代わりをラムダ式で書くとき、別インスタンスになると思い込んでいて、インスタンス単位での管理で問題となったメモです。

Android Architecture Components の LiveData を使って、1つの LiveData が管理しているデータソースを別の LiveData としても提供するということを実現するために、空の Observer を LiveData.observer に渡す以下のような実装でハマりました。

サンプルコード

```kotlin:kotlin
// observeされている間だけ1秒おきにインクリメントしていくカウンターLiveData
class Counter : LiveData<Int>() {
    private var timer: Timer? = null

    // アウタークラスのカウンターに依存しつつ、偶数だけを配信するLiveData
    var oddCounter: MutableLiveData<Int> = object : MutableLiveData<Int>() {
        override fun observe(owner: LifecycleOwner, observer: Observer<Int>) {
            super.observe(owner, observer)

            // アウタークラスにカウンターの実体があるため、
            // そちらも observe することで active にし、カウンターを開始する
            this@Counter.observe(owner, Observer<Int> { })
        }
    }

    override fun onActive() {
        val task = object : TimerTask() {
            override fun run() {
                var nextCount = (value ?: 0) + 1

                postValue(nextCount)
                if (nextCount % 2 != 0) {
                    oddCounter.postValue(nextCount)
                }
            }
        }
        timer = Timer()
        timer?.scheduleAtFixedRate(task, 0, 1000)
    }

    override fun onInactive() {
        timer?.cancel()
    }
}

class MainKotlinActivity : AppCompatActivity() {
    private val counter = Counter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 複数のownerがobserveする実装のサンプルで、アクティビティとプロセスがobserveするコード
        counter.oddCounter.observe(this, Observer<Int> { value ->
            value?.let { println("activity got $it") }
        })
        counter.oddCounter.observe(ProcessLifecycleOwner.get(), Observer<Int> { value ->
            value?.let { println("process got $it") }
        })
    }
}
```

これを実行すると、 `this@Counter.observe(owner, Observer<Int> {})` というコードが、異なる `owner` インスタンスに対して2回呼び出されますが、2回目で `java.lang.IllegalArgumentException: Cannot add the same observer with different lifecycles` という例外が発生します。

原因は、次の通り。

- `Observer<Int> { }` は、何度実行しても（同じクラス内では）同じインスタンスを返す
- `LiveData.observe` の[仕様] (https://developer.android.com/reference/androidx/lifecycle/LiveData.html#observe(androidx.lifecycle.LifecycleOwner,%2520androidx.lifecycle.Observer%3C?%2520super%2520T%3E)) で、1つのobserverを異なるownerでは使えない

おそらく、 `Observer<Int> { }` だとクロージャが呼び出し元のスコープに依存しておらず固定であるため、1つのインスタンスを流用しているんだろう。
実際、 `Observer<Int> { }` を `Observer<Int> { print(owner.toString()) }` とする、つまり呼び出しのたびに変わる `owner` に依存する実装になっていれば、別インスタンスを返しました。

そもそもバイトコードとしては匿名インナークラスとラムダ式だと全く異なるんだろうと思い、 http://www.ne.jp/asahi/hishidama/home/tech/java/lambda.html#h_invokedynamic このへんを読んでフムフム。

---

今回、空実装のObserverインスタンスを作りたくて `Observer<Int> { }` と書いてしまっていたわけですが、IDE の出す warning に従ったらそうなってしまったという背景があります。実際、以下のようにいくつかの方法で書けるわけですが。。

もっともオーソドックスなやり方: Java の匿名インナークラス。

```java:java
Observer<Integer> nullObserver = new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer integer) {
    }
};
observe(owner, nullObserver);
```

でもこれは、Android Studio に「can be replaced with lambda」と提案され、それに従うと以下のようにラムダ式を使うコードに変換されます。

```java:java
Observer<Integer> nullObserver = value -> {};
observe(owner, nullObserver);
```

Kotlinでも同様。

```kotlin:kotlin
val nullObserver = object : Observer<Int> {
    override fun onChanged(t: Int?) {
    }
}
observe(owner, nullObserver);
```

```kotlin:kotlin
val nullObserver = Observer<Int> { }
observe(owner, nullObserver);
```

それぞれの違いを意識して使い分けないといかんなと、再認識しました。

単にもとの匿名インナークラスやオブジェクト式のままにしておい場合、別の人がIDEの提案に従って再度同じ変更をしてしまう可能性もあるため、以下のように専用のクラスをインスタンス化するようにしました。

```kotlin:kotlin
private class NullObserver<T> : Observer<T> {
    override fun onChanged(t: T?) {
    }
}

observe(owner, NullObserver<Int>())
```

いまqiita記事をまとめながら、ソースコードコメントでなにか補足しておいても良かったのかなとも思いました。

おわり。

---

ちなみに、ここに書いたサンプルコードだと [Transformations ](https://developer.android.com/reference/kotlin/androidx/lifecycle/Transformations) を使えばこんなややこしい空observeみたいなことをしなくてすむ気もしますが、実際のコードはもう少し込み入った事情があったりはします。