How does Kotlin lambda perform compared to Anonymous inner class? - android

Android Studio suggests to replace anonymous inner class with lambda.
titleTextView.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
Log.d("MY_TAG", "textView clicked in anonymous inner class")
}
})
Decompiled Java code:
var10000 = this.titleTextView;
if (this.titleTextView == null) {
Intrinsics.throwUninitializedPropertyAccessException("titleTextView");
}
var10000.setOnClickListener((OnClickListener)(new OnClickListener() {
public void onClick(#Nullable View v) {
Log.d("MY_TAG", "textView clicked in anonymous inner class");
}
}));
Before lambda, to avoid creation of new object for each of views that were set OnClickListener, it was better to have Activity/Fragment implement View.OnClickListener interface or use Butterknife's #OnClick annotation.
How different the performance will be with lambda like below?
titleTextView.setOnClickListener { Log.d("MY_TAG", "textView clicked in lambda") }
Decompiled Java code:
TextView var10000 = this.titleTextView;
if (this.titleTextView == null) {
Intrinsics.throwUninitializedPropertyAccessException("titleTextView");
}
var10000.setOnClickListener((OnClickListener)null.INSTANCE);
In case of lambda I don't see the Log.d("MY_TAG", "textView clicked in lambda") in decompiled code.

The performance of a lambda will be at least as good as creating an anonymous inner class.
If it doesn't capture any references, the compiler will make it a singleton inside whatever class it's used in as an optimization measure. This is what happens in your case, as the contents of your listener don't refer to anything outside of the lambda. (This singleton instance is what the null.INSTANCE is trying to refer to, the decompiler just has trouble with resolving the name of the class generated for the lambda.) So in this case, the cost of the lambda is just 1 object allocation.
If your lambda does capture something, e.g. like this:
val random = Random().nextInt()
titleTextView.setOnClickListener {
Log.d("MY_TAG", "textView clicked in lambda, random value was $random")
}
... then whenever you set the listener, a new instance will have to be allocated, because these instances have to store references to variables, which might be different each time they're created. In this case, you get as many object allocations as many times you run the method the lambda's in. Note that if this is only done during setup, like in onCreate, this will mean just 1 object allocation as well.
So you may get:
0 extra allocations, if your listeners are methods of your already existing class (Fragment or Activity).
1 extra allocation if you use a lambda that doesn't capture anything.
N extra allocations if you use a lambda that captures something, N being the number of times you run the code where the lambda is used, which could be 1 if this is only during initialization.
N extra allocations if you use an anonymous inner class, as there's no optimization there for non-capturing classes. Again, this may actually be 1 in a lot of cases.
Even though using methods inside the existing class would mean 0 additional allocations, I wouldn't go with that approach for performance - any gains will probably be completely unnoticeable. Go with the solution that's more readable and maintainable for you instead.

Related

Is it safe to use function references to register and unregister a listener?

I recently found myself writing the following code:
fun listener() {
// Do some stuff
adapter.removeLoadStateListener(::listener)
}
adapter.addLoadStateListener(::listener)
A colleague remarked on the fact that
val x1 = ::listener
val x2 = ::listener
x1 == x2 //true
x1 === x2 //false
However,
var mySet = mutableSetOf<() -> Unit>()
fun a() { }
fun b() { }
mySet.add(::a)
mySet.add(::b)
mySet.remove(::a)
mySet.contains(::a) // false
mySet.contains(::b) // true
Based on this I get the impression I do not understand ::myFun properly and I start to question if my original code is safe to use.
TL;DR
Is it safe to use ::listener to reference a method to be used as a listener and that will need to be referenced multiple times (e.g. add + remove)?
What is actually going on behind the scenes regarding anonymous classes etc.?
EDIT
Eventually we decided against using ::listener for this case. However, we ran into a new problem since add/removeLoadStateListener will only accept the (CombinedLoadState) -> Unit type.
I'll leave our solution to that problem here for future reference and other readers as it is related (I expect some may even come here looking for this rather than answers to the original question):
val listener = object : (CombinedLoadStates) -> Unit {
override operator fun invoke(loadState: CombinedLoadStates) {
// Do some stuff
adapter.removeLoadStateListener(this)
}
}
adapter.addLoadStateListener(listener)
When you use ::listener, you are sort of creating an anonymous class that implements the interface, each time. However, as your test shows, the .equals of the anonymous classes will return that they are equal, which usually wouldn't be the case with an anonymous class (created with object: syntax). So, two instances created using ::listener will be equivalent with == but not with ===.
Sets typically use .equals equality (==) to determine if an instance is a duplicate, but it's possible to create a Set backed by IdentityHashMap so it effectively behaves as a Set using identity comparison. This breaks the Set contract, but a class could be using it internally for some reason.
Whether it is safe to use this depends on whether the class you're working with compares listener instances by .equals or identity. If it's possible it's using an IdentityHashMap to store and compare listeners, then this is not safe.

How to create interchangeable class types for android fake testing?

I'm trying to create a fake class for my repository to test a view model.
As far as I understood, the key element here is to create two classes with a common interface so both classes would contain the same methods.
The problem is I get a Type mismatch when trying to initialize an object.
I tried to do the same in a simplified manner:
class fakeClass1 : fakeInterface {
override fun getAllData(): String {
return ""
}}}
class fakeClass2 : fakeInterface {
override fun getAllData(): String {
return ""
}}
interface fakeInterface {
fun getAllData(): String}
val fakeClass: fakeClass1 = fakeClass2()
But that didn't work either.
What am I missing?
Ok, I figured it out.
I was wrong to think that those two classes should be interchangeable.
I solved it by making the ViewModel take the common interface in its constructor instead of the actual repository class. This allows the ViewModel to take any class which implement this interface as it's repository.
I think you worked it out, but just so you're clear (this is an important, fundamental thing!)
val fakeClass: fakeClass1 = fakeClass2()
This is defining a variable called fakeClass that refers to an object with the fakeClass1 type. Then you assign an object with the fakeClass2 type.
But a fakeClass2 is not a fakeClass1, neither is a superclass of the other, so you can't treat one as the other. Your example is simple, but imagine you added coolFunction() to fakeClass1 - they'd now happen to have different structures, and trying to call that method on an object that doesn't have it would cause a crash.
The only thing those classes have in common, is that they both have the fakeInterface type - they are fakeInterfaces, and that guarantees they implement the stuff in that interface (your getAllData function in this case). So if you treat them both as that type instead:
val fakeClass: fakeInterface = fakeClass2()
you can use either one, because they're both fakeInterfaces (similar to how Ints and Doubles are different but they're both Numbers). Because fakeClass is now a fakeInterface, you can only access the functions and properties that a fakeInterface has - you can't call coolFunction() even if you happened to pass in a fakeClass1, because fakeInterface doesn't have that.
(You could cast the variable to fakeClass1, basically saying "oh by the way this object is actually this type as well", but at that point the type system can't guarantee you're correct unless you're explicitly checking fakeClass is fakeClass1, and it'll warn you if that's the case)
The Java tutorials are pretty good and they'll give you an overview about how the types each form a kind of "contract" you work with

How to save and restore lambdas in Android?

When implementing state restoration in Android, how can I save and restore a lambda?
I tried saving it as Serializable and Parcelable, but it throws a compile error.
Is there any way to save and restore them, or should I seek other approaches?
Kotlin lambdas implement Serializable, so they can't be saved like:
override fun onSaveInstanceState(outState: Bundle) {
outState.putSerializable("YOUR_TAG", myLambda as Serializable)
super.onSaveInstanceState(outState)
}
Similarly, to restore them:
override fun onCreate(savedInstanceState: Bundle?) {
myLambda = savedInstanceState?.getSerializable("YOUR_TAG") as (MyObject) -> Void
super.onCreate(savedInstanceState)
}
(This can obviously be done in any of the lifecycle events that offer you the savedInstanceState, as this was just an example)
Some notes:
When saving them, they need to be casted, otherwise compiler complains (for some reason).
import java.io.Serializable is required.
The method where you're casting it back to your lambda type will throw a warning Unchecked cast: Serializable? to YourLambdaType. This cast is safe (assuming you infer the nullability correctly!), so you can safely supress this warning by using #Suppress("UNCHECKED_CAST")
MyObject must be Serializable or Parcelable, otherwise it crashes in runtime.
Now there's a detail that is not told anywhere and crashes in runtime with no helpful crash logs. The inner implementation of your lambda (i.e. what's inside the { } when you assign it) must not have references to objects that will be deallocated in a later moment.
A classic example would be:
// In your MyActivity.kt…
myLambda = { handleLambdaCallback() }
…
private fun handleLambdaCallback() {
…
}
This will crash in runtime because handleLambdaCallback is implicitly accessing this, which would trigger an attempt to recursively serialize the entire object graph reachable by it, which would fail at some point during serialization time.
One solution to this problem is to send a reference in the lambda. Example:
// In your MyActivity.kt…
myLambda = { fragment -> (fragment.activity as MyActivity).handleLambdaCallback() }
…
private fun handleLambdaCallback() {
…
}
This way, we are computing the reference when the lambda is invoked, rather than when it's assigned. Definitely not the cleanest solution, but it's the best I could come with, and it works.
Feel free to suggest improvements and alternative solutions!
Should I seek other approaches?
Yes, there is not a really good reason to do it, your code won't be easily testable and you could introduce memory leaks.
Instead of saving the function, save the parameters (i.e. variables in the scope) that are needed to be saved, and invoke the function as you usually do.
Example
Instead of doing
val name = "John Smith"
val sayHello = { "Hi there, $name" }
startActivity(Intent().apply { putExtra("GREETER", sayHello as Serializable) })
Create a function that you can use elsewhere
fun sayHello(name: String) = { "Hi there, $name" }
And invoke with the restored name parameter later
There are various alternatives,
You may reassign the lambdas on the parent's onAttachFragment method, or via callbacks on the fragment's onAttach method.
You may create a ViewModel for the fragment that hosts that data so that it can be saved between states
You may use a FragmentFactory that receives the object with lambdas so that new fragments recreated regain access to that data which is not destroyed from the factory.
There's an old deprecated way of simply using retainInstance so that you don't care of fragment being destroyed during these state changes. Of course this consumes more data for your app.

Kotlin: Referencing anonymous object from itself / inside (via this)

TL;DR
These object : someClass{ } anonymous objects can't access itself via this (which results the outer object). How can I access it?
Longer explanation:
For my Fragment I need a PreDrawListener. I call this in onCreateView. When executing, I wanted to remove the listener afterwards. So the Java way of doing would suggest something like this
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
val treeObserver = layout.viewTreeObserver
treeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
layout.viewTreeObserver.removeOnPreDrawListener(this)
...
}
}
The problem is, when taking a look at the removeOnPreDrawListener(this) the this object is not the listener but myFragment$onCreateView$1#f019bf0
Alternatively I could access this#MyFragment which returns the reference to the Fragment directly.
Still, none of these options seems to be my PreDrawListener. How can I access it from inside (if at all)?
I honestly don't see your problem.
this inside an anonymous refers to the class itself, but they never have names. You can't create an anonymous class with a name. To demo this, I wrote some sample code:
class TheClass{
fun run(){
val anon = object: Runnable {
override fun run() {}
}
println(anon::class.java.simpleName)
println(anon::class.java.name)
}
}
Which prints:
run$anon$1
com.package.TheClass$run$anon$1
Now, this is nice and all, but it still doesn't look like yours. But you see it matches the containing class, method, variable, and finally the dollar sign denoting that it's an anonymous inner class. That applies to the second, which is the full one. The first just prints the short name, which is the method, var name, and again the dollar sign that shows it's anonymous function.
If you're interested in why the dollar sign with a number appears, see this. T
Let's expand that and ditch the variable. Obviously, this is horrible code (and far from memory-efficient, but it's a demo so it doesn't matter):
class TheClass {
fun run(){
println(object: Runnable {
override fun run() { }
})
}
}
This prints, and matching your pattern:
com.package.TheClass$run$anon$1
You've seen the pattern; now you can start "decoding" the hash you got:
myFragment // inside myFragment
$onCreateView // Inside a function
$1 // There is an anonymous class with a specific identifier
#f019bf0 // This is standard everywhere; just look up Object.toString()
What I just tried to prove is: this does refer to the anonymous function you create. Anonymous functions are, well, anonymous. They don't have names. They use $number as identifiers. So if you have this code:
treeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
layout.viewTreeObserver.removeOnPreDrawListener(this)
...
}
}
this will refer to the listener, even though printing the class may print stuff that looks confusing. If there's something that's broken, it's not because of this not referring to the listener (because it does)
Also, your code compiles fine. There's no type mismatch in there either. If it referred to a different object, it wouldn't work if you passed this to a method that requires a OnPreDrawListener.
You would get a different result with the same code in Java. This is because Kotlin compiles anonymous functions as Class$function$number, where as Java compiles it to Class$number. If it's in a nested class, it will appear as Outer$Inner$function$number in Kotlin, and Outer$Inner$number in Java.
It's a difference in the compiler that results in different names; Java excludes the function, where as Kotlin includes it. It's in the .class filename, so if you build your project and look at the file names in a file explorer for whatever OS you have (Do not look in IntelliJ. It will decompile the files for you. And remember, you're just looking for the name, which IntelliJ messes up by merging the .class files into a single one to match the original source)
Just as final meta, I do print the class instead of printing the object. Interfaces do not have an overridden toString method, which means it defaults to the one on Object, which returns getClass().getName() + "#" + Integer.toHexString(hashCode()); (original code can be found here). println(this) is the same as println(this.toString()), which calls the toString method in Object, which prints the class name. println(this) is the same as printing the object or printing the class

Memory leak in Java, but not in Kotlin (of same code base)... why? [duplicate]

This question already has answers here:
Kotlin : safe lambdas (no memory leak)?
(3 answers)
Closed 5 years ago.
I have a piece of simple code below in an activity...
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
}
});
valueAnimator.start();
}
}
If the activity got terminated, there will be memory leak (as proven by Leak Canary).
However, when I covert this code to identical Kotlin code (using shift-alt-command-k), it is as below
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
valueAnimator.repeatCount = ValueAnimator.INFINITE
valueAnimator.addUpdateListener { }
valueAnimator.start()
}
}
Memory leak no longer happen. Why? Is it because the anonymous class object got converted to Lambda?
The difference between these 2 versions is pretty simple.
Java version of the AnimatorUpdateListener contains implicit reference to the outer class (MainActivity in your case). So, if the animation keeps running when the activity is not needed anymore, the listener keeps holding the reference to the activity, preventing it from being garbage-collected.
Kotlin tries to be more clever here. It sees that the lambda which you pass to the ValueAnimator does not reference any objects from the outer scope (i.e. MainActivity), so it creates a single instance of AnimatorUpdateListener which will be reused whenever you [re]start the animation. And this instance does not have any implicit references to the outer scope.
Side note: if you add the reference to some object from outer scope to your lambda, Kotlin will generate the code which creates a new instance of the update listener every time the animation is [re]started, and these instances will be holding the implicit references to MainActivity (required in order to access the object(s) which you decide to use in your lambda).
Another side note: I strongly recommend to read the book called "Kotlin in Action" as it contains lots of useful information on Kotlin in general, and my explanation of how Kotlin compiler make a choice about whether to put the implicit reference to outer scope into the object created after SAM conversion or not comes from this book.
1. Check what's actually going on
I think you'll find the “Show Kotlin Bytecode” view very helpfull in seeing exactly what is going on. See here for the InteliJ shortcut.
(Would have done this for you, but hard without greater context of your application)
2. JVM similarities, but Kotlin explicit differences
But since Kotlin runs on the same JVM as Java (and so uses the same garbage collector as Java) you should expect a similarly safe runtime environment. That being said, when it comes to Lamdas and explicit references, when they're converted. And it can vary in different cases:
Like in Java, what happens in Kotlin varies in different cases.
If the lambda is passed to an inline function and isn’t marked noinline, then the whole thing boils away and no additional classes
or objects are created.
If the lambda doesn’t capture, then it’ll be emitted as a singleton class whose instance is reused again and again (one class+one
object allocation).
If the lambda captures then a new object is created each time the lambda is used.
Source: http://openjdk.java.net/jeps/8158765
3. Summary, and further reading
This answer should explain what your seeing, couldn't have explained it better myself: https://stackoverflow.com/a/42272484/979052
Different question, I know, but the theory behind it is the same - hope that helps
As you already suggested, the argument to addUpdateListener is actually different in both versions.
Let's see an example. I've created a class JavaAbstract with a single abstract method foo:
public interface JavaInterface {
void foo();
}
This is used in JavaInterfaceClient:
public class JavaInterfaceClient {
public void useInterfaceInstance(JavaAbstract inst){
inst.foo();
}
}
Let's see how we can call useInterfaceInstance from Kotlin:
First, with a simple lambda as in your example (SAM Conversion):
JavaInterfaceClient().useInterfaceInstance {}
The resulting bytecode represented in Java:
(new JavaInterfaceClient()).useInterfaceInstance((JavaInterface)null.INSTANCE);
As you can see, very simple, no object instantiations.
Second, with an anonymous instance:
JavaInterfaceClient().useInterfaceInstance(object : JavaInterface {
override fun foo() {
}
})
The resulting bytecode represented in Java:
(new JavaInterfaceClient()).useInterfaceInstance((JavaInterface)(new JavaInterface() {
public void foo() {
}
}));
Here we can observe new object instantiations, which defers from the SAM conversion / lambda approach. You should try the second example in your code.

Categories

Resources