为何Kotlin Lambda会被垃圾回收而普通对象不会?
1. 现象的核心原因
问题出在Kotlin对SAM转换的处理逻辑,以及两种监听器实例的引用持有关系差异:
匿名对象(
anObject):它是直接实现Notifications.Listener的实例,被companion object的val anObject强引用持有。由于companion object是单例,生命周期和应用一致,这个对象始终有强引用兜底,因此弱引用永远不会被GC回收,get()方法始终返回非null。Lambda(
aLambda):它本身是一个函数类型对象((Bundle?, Fragment?) -> Unit),而非Listener接口实例。当调用addListener(aLambda)时,Kotlin会自动执行SAM转换:临时生成一个Listener接口的实现类实例,这个实例的onNotification方法会委托调用aLambda函数。但关键是:这个SAM转换生成的Listener实例,并没有被任何强引用持有——companion object只持有aLambda函数对象的强引用,不持有转换后的Listener实例。因此这个Listener实例只有Notifications中的弱引用指向它,GC触发时就会被回收,导致弱引用get()返回null。
简单来说:你持有了Lambda的引用,但这和转换后的Listener实例是两个不同的对象,后者没有强引用兜底,自然会被GC回收。
2. 这是Kotlin的预期行为吗?
是的,这是Kotlin SAM转换机制的预期设计:
SAM转换是按需临时生成实例,编译器不会自动将转换后的接口实例存储到强引用变量中,除非你显式地将其赋值并持有。比如修改代码,把SAM转换后的实例存起来:
val samListener: Notifications.Listener = aLambda Notifications.addListener(samListener)
此时samListener被companion object强引用,对应的Listener实例就不会被GC回收,和匿名对象的表现完全一致。
原始代码参考
Kotlin代码
companion object { // This lambda IS being garbage collected after a while val aLambda: (bundle: Bundle?, caller: Fragment?) -> Unit = { _, _ -> if (something()) processRequest() } // This object IS NOT being garbage collected val anObject = object: Notifications.Listener { override fun onNotification(bundle: Bundle?, caller: Fragment?) { if (something()) processRequest() } } init { // This Notifications class holds weak references to both the lambda & object the same way Notifications.addListener(aLambda) Notifications.addListener(anObject) } }
Java代码
public class Notifications { private static ArrayList<WeakReference<Listener>> listenersArray = new ArrayList<>(); public static void addListener(@NonNull Listener listener) { listenersArray.add(new WeakReference<>(listener)); } public static void notify(@Nullable Bundle bundle, @Nullable Fragment caller) { for (WeakReference<Listener> weakListener : listenersArray) { Listener listener = weakListener.get(); if (listener != null) { listener.onNotification(bundle, caller); } } } }
内容的提问来源于stack exchange,提问作者Doron Yakovlev Golani

