Java (Kotlin / JVM) lambda expressions are not always separate instances

When writing instead of Java's anonymous inner class with a lambda expression, I assumed that it would be a separate instance, and this is a memo that became a problem in managing each instance.

In order to realize that the data source managed by one LiveData can be provided as another LiveData by using LiveData of Android Architecture Components, the following implementation passes an empty Observer to LiveData.observer. I was addicted to it.

Sample code

kotlin


//Counter LiveData that increments every second only while being observed
class Counter : LiveData<Int>() {
    private var timer: Timer? = null

    //LiveData that delivers only even numbers while relying on outer class counters
    var oddCounter: MutableLiveData<Int> = object : MutableLiveData<Int>() {
        override fun observe(owner: LifecycleOwner, observer: Observer<Int>) {
            super.observe(owner, observer)

            //Because there is a counter in the outer class
            //By observing that too, make it active and start the counter.
            [email protected](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)

        //Code that activities and processes observe in a sample implementation that is observed by multiple owners
        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") }
        })
    }
}

When I do this, the code [email protected] (owner, Observer <Int> {}) is called twice for different ʻowner instances, but the second time it is java.lang.IllegalArgumentException. I get the exception: Cannot add the same observer with different lifecycles`.

The cause is as follows.

--ʻObserver {} returns the same instance (within the same class) no matter how many times it is run --[Specifications] of LiveData.observe` (https://developer.android.com/reference/androidx/lifecycle/LiveData.html#observe (androidx.lifecycle.LifecycleOwner,% 2520androidx.lifecycle.Observer% 3C?% 2520super" % 2520T% 3E)), one observer cannot be used by different owners

Probably, ʻObserver {} is diverting one instance because the closure does not depend on the scope of the caller and is fixed. In fact, unless ʻObserver <Int> {} is ʻObserver {print (owner.toString ())}, that is, the implementation depends on ʻowner, which changes with each call. Returned an instance.

In the first place, I think that the bytecode is completely different from the anonymous inner class and the lambda expression, so http://www.ne.jp/asahi/hishidama/home/tech/java/lambda.html#h_invokedynamic Read this Hum Hum.


This time, I wrote ʻObserver {}` because I wanted to create an Observer instance with an empty implementation, but there is a background that it happened when I followed the warning issued by the IDE. In fact, it can be written in several ways as follows. ..

The most orthodox way: Java's anonymous innerclass.

java


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

But this is suggested to Android Studio as "can be replaced with lambda", which translates to code that uses a lambda expression as follows:

java


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

Same with Kotlin.

kotlin


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

kotlin


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

I realized again that I should be aware of the differences between them and use them properly.

If you simply leave the original anonymous inner class or object expression, another person may make the same change again according to the IDE's suggestion, so instantiate a dedicated class as follows: I made it.

kotlin


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

observe(owner, NullObserver<Int>())

While compiling the qiita article now, I wondered if it was okay to supplement something with source code comments.

end.


By the way, with the sample code written here, if you use Transformations, you will not have to do such a complicated sky observe. However, the actual code may be a little more complicated.

Recommended Posts

Java (Kotlin / JVM) lambda expressions are not always separate instances
Understand Java 8 lambda expressions
About Java lambda expressions
Explain Java 8 lambda expressions
[Java] Introduction to lambda expressions
[Introduction to Java] About lambda expressions
Getting started with Java lambda expressions
The origin of Java lambda expressions
How to use Java lambda expressions
I tried to summarize Java lambda expressions
Nowadays Java lambda expressions and Stream API
Organize your own differences in writing comfort between Java lambda expressions and Kotlin lambda expressions.