Backrgound
Anonymous inner classes maintain references to their enclosing class instances, which can lead to memory leaks.
The question arises: do lambda expressions introduce similar risks?
Anonymous Inner Classes vs Lambda Expressions
Consider the following example class TestInner, which includes both an anonymous inner class and a lambda expression:
public class TestInner {
public void test() {
new Thread(() -> {
Log.i("test", "dddd");
}).start();
}
public void test1() {
new Thread(new Runnable() {
@Override
public void run() {
Log.i("test", "dddd1");
}
}).start();
}
}
When compiled, the anonymous inner class creates a separate class file named TestInner$1. This class holds a reference to the outer class instance through its constructor:
.method constructor <init>(Lcom/example/jnihelper/TestInner;)V
.registers 2
.param p1, "this$0"
iput-object p1, p0, Lcom/example/jnihelper/TestInner$1;->this$0:Lcom/example/jnihelper/TestInner;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
This confirms that anonymous inner classes hold references to their enclosing instances, potentially causing memory leaks.
In contrast, the lambda expression generates a synthetic class TestInner$$ExternalSyntheticLambda0 that does not capture the outer class instance directly. Instead, it calls a static helper method:
.method public final run()V
.registers 1
invoke-static {}, Lcom/example/jnihelper/TestInner;->lambda$test$0()V
return-void
.end method
However, if the lambda body accesses fields from the outer class explicitly, the compiler will generate a non-static method and retain a reference to the outer instance:
public void test() {
new Thread(() -> {
Log.i("test", "dddd");
Log.i("test", TestInner.this.helloInner);
}).start();
}
After compilation, the generated lambda class will contain a field referencing the outer class:
.class public final synthetic Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
super Ljava/lang/Object;
implements Ljava/lang/Runnable;
.field public final synthetic f$0:Lcom/example/jnihelper/TestInner;
.method public synthetic constructor <init>(Lcom/example/jnihelper/TestInner;)V
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
iput-object p1, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
return-void
.end method
.method public final run()V
iget-object v0, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
invoke-virtual {v0}, Lcom/example/jnihelper/TestInner;->lambda$test$0$com-example-jnihelper-TestInner()V
return-void
.end method
Conclusion
Anonymous inner classes retain references to their outer class, posing potential memory leak risks.
Lambda expressions do not inherently cause memory leaks unless they explicitly reference members of the outer class, in which case the outer instance is captured.