android - Kotlin : safe lambdas (no memory leak)?

阅读后this article about Memory Leaks ,我想知道在 Kotlin Android 项目中使用 lambdas 是否安全。确实,lambda 语法让我更轻松地编程,但是内存泄漏呢?

作为问题的一个例子,我从我的一个项目中获取了一段代码,我在其中构建了一个 AlertDialog。这段代码在我项目的 MainActivity 类中。

fun deleteItemOnConfirmation(id: Long) : Unit {
        val item = explorerAdapter.getItemAt(id.toInt())
        val stringId = if (item.isDirectory) R.string.about_to_delete_folder else R.string.about_to_delete_file

        val dialog = AlertDialog.Builder(this).
                setMessage(String.format(getString(stringId), item.name)).setPositiveButton(
                R.string.ok, {dialog: DialogInterface, id: Int ->
                        val success = if (item.isDirectory) ExplorerFileManager.deleteFolderRecursively(item.name)
                        else ExplorerFileManager.deleteFile(item.name)
                        if (success) {
                            explorerAdapter.deleteItem(item)
                            explorerRecyclerView.invalidate()
                        }
                        else Toast.makeText(this@MainActivity, R.string.file_deletion_error, Toast.LENGTH_SHORT).show()
                    }).setNegativeButton(
                R.string.cancel, {dialog: DialogInterface, id: Int ->
                    dialog.cancel()
        })

        dialog.show()
}

我的问题很简单:为正负按钮设置的两个 lambdas 会导致内存泄漏吗? (我的意思是,kotlin lambdas 是否简单地转换为 Java 匿名函数?)

编辑:也许我已经得到了答案 in this Jetbrains Topic .

最佳答案

编辑(2017 年 2 月 19 日):我收到了一份非常全面的 reply来自 Mike Hearn 关于这个问题:

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.

Thus it is similar behaviour to Java except for the inlining case where it's even cheaper. This efficient approach to encoding lambdas is one reason why functional programming in Kotlin is more attractive than in Java.


编辑(2017 年 2 月 17 日):我已在 Kotlin discussions 中发布了有关此主题的问题。 .也许 Kotlin 工程师会带来一些新的东西。


are kotlin lambdas simply converted to Java Anonymous functions ?

我自己也在问这个问题(这里有一个简单的更正:这些被称为 匿名类,而不是函数)。 Koltin 文档中没有明确的答案。他们只是state那个

Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure, i.e. those variables that are accessed in the body of the function.

在函数体中访问的变量有点令人困惑。对封闭类实例的引用是否也计算在内?

我已经看到您在问题中引用的主题,但目前看来它已经过时了。我找到了更多最新信息here :

Lambda expression or anonymous function keep an implicit reference of the enclosing class

因此,不幸的是,Kotlin 的 lambda 似乎与 Java 的匿名内部类存在相同的问题。

为什么匿名内部类不好?

来自Java specs :

An instance i of a direct inner class C of a class O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created

这意味着匿名类将始终具有对封闭类实例的隐式引用。而且由于引用是隐式的,因此无法摆脱它。

看一个简单的例子

public class YourActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Thread(new Runnable() {
                 // the inner class will keep the implicit reference to the outer activity
                @Override
                public void run() {
                 // long-running task
                }
        }).start();
   }
}

如您所见,在这种情况下,在执行长时间运行的任务之前会出现内存泄漏。一种解决方法是使用静态嵌套类。

由于 Kotlin 的 非内联 lambda 持有对封闭类实例的引用,因此它们在内存泄漏方面存在类似问题。

奖励:与其他 Lambda 实现的快速比较

Java 8 Lambda

语法:

  • 声明SAM(单一抽象方法)接口(interface)

    interface Runnable { void run(); }
    
  • 将此接口(interface)用作 lambda 的类型

    public void canTakeLambda(Runnable r) { ... }
    
  • 传递你的 lambda

    canTakeLambda(() -> System.out.println("Do work in lambda..."));
    

内存泄漏问题: 如 specs 中所述:

References to this -- including implicit references through unqualified field references or method invocations -- are, essentially, references to a final local variable. Lambda bodies that contain such references capture the appropriate instance of this. In other cases, no reference to this is retained by the object.

简单地说,如果您不使用封闭类中的任何字段/方法,则不会像匿名类那样对 this 进行隐式引用。

Retrolambda

来自 docs

Lambda expressions are backported by converting them to anonymous inner classes. This includes the optimization of using a singleton instance for stateless lambda expressions to avoid repeated object allocation.

我猜,这是不言自明的。

苹果的 Swift

语法:

  • 声明类似于 Kotlin,在 Swift 中 lambda 被称为闭包:

    func someFunctionThatTakesAClosure(closure: (String) -> Void) {}
    
  • 通过闭包

    someFunctionThatTakesAClosure { print($0) }
    

    这里,$0 指的是闭包的第一个 String 参数。这对应于 Kotlin 中的 it。注意:与 Kotlin 不同,在 Swift 中,我们还可以引用其他参数,例如 $1$2 等。

内存泄漏问题:

在 Swift 中,就像在 Java 8 中一样,闭包仅在访问实例的属性时才会捕获对 self(Java 和 Kotlin 中为 this)的强引用,例如 self.someProperty,或者如果闭包调用实例上的方法,例如 self.someMethod()

开发人员还可以轻松地指定他们只想捕获弱引用:

   someFunctionThatTakesAClosure { [weak self] in print($0) }

我希望在 Kotlin 中也有可能 :)

关于android - Kotlin : safe lambdas (no memory leak)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42271208/

相关文章:

android - IllegalArgumentException : savedInstance

android - 无法设置约束组的可见性

gradle - 为什么更新 Gradle 到 3.1.0 后显示 "3rd-party Gradl

java - Kotlin 中的 Lambda 表达式

kotlin - 如果可空类型为空,我如何运行代码块?

android - android 上的 Kotlin : Cannot find main mer

kotlin - Kotlin 中的 'open' 和 'public' 有什么区别?

class - 我无法从 Kotlin 的嵌套类中联系到任何类成员

function - Kotlin:你可以为可变参数使用命名参数吗?

java - 在使用 Kotlin 的方法中抛出异常