Kotlin:安全的Lambda(无内存泄漏)吗? [英] Kotlin : safe lambdas (no memory leak)?

查看:832
本文介绍了Kotlin:安全的Lambda(无内存泄漏)吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

已阅读这篇关于内存泄漏的文章,我想知道在Kotlin Android项目中使用lambda是否安全.确实,lambda语法使我的编程更加轻松,但是内存泄漏呢?

After having read this article about Memory Leaks, I am wondering whether using lambdas in Kotlin Android project is safe. It's true that lambda syntax makes me program with more ease, but what about the Memory Leaks ?

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

As an example of the problematic, I've taken a piece of code from one of my projects, where I build an AlertDialog. This code is inside the MainActivity class of my project.

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()
}

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

My question is very simple : can the two lambdas set for positive and negative buttons lead to Memory Leaks ? (I also mean, are kotlin lambdas simply converted to Java Anonymous functions ?)

也许我已经回答了在这个Jetbrains主题中.

推荐答案

编辑(2017年2月19日)::我收到了非常全面的

Edit (February 19, 2017): I received a very comprehensive reply from Mike Hearn regarding this issue:

就像Java中一样,在Kotlin中发生的情况因情况而异.

Like in Java, what happens in Kotlin varies in different cases.

  • 如果将lambda传递给内联函数且未标记为noinline,则整个过程将消失,并且没有其他类或 对象已创建.
  • 如果未捕获lambda,则它将作为单例类发出,其实例被一次又一次地重用(一个类+一个对象 分配).
  • 如果lambda被捕获,则每次使用lambda都会创建一个新对象.
  • 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.

因此,除了内联大小写外,其行为与Java类似 更便宜的地方.这种编码lambda的有效方法 是Kotlin中的函数式编程更具吸引力的原因之一 比Java中的要好.

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的工程师会带来一些新的东西.


Edit (February 17, 2017): I've posted a question regarding this topic in the Kotlin discussions. Maybe Kotlin engineers will bring something new to the table.

kotlin lambdas是否可以简单地转换为Java匿名函数?

are kotlin lambdas simply converted to Java Anonymous functions ?

我自己在问这个问题(这里是一个简单的更正:这些叫做 Anonymous Classes ,不是函数). Koltin文档中没有明确的答案.他们只是声明

I was asking this question myself (one simple correction here: these are called Anonymous Classes, not functions). There is no clear answer in the Koltin documentation. They just state that

使用高阶函数会产生某些运行时惩罚:每个 函数是一个对象,它捕获一个闭包,即那些变量 在函数主体中访问的内容.

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.

在函数体内访问的变量的含义有些混淆.对封闭类实例的引用也算在内吗?

It is a bit confusing what they mean by variables that are accessed in the body of the function. Is the reference to the instance of the enclosing class also counted?

我已经看到了您在问题中引用的主题,但目前看来已经过时了.我发现了更多最新信息

I've seen the topic you are referencing in your question but it seems it is outdated as for now. I've found more up-to-date information here:

Lambda表达式或匿名函数保留以下内容的隐式引用 封闭类

因此,不幸的是,看来Kotlin的lambda与Java的Anonymous Inner Classes具有相同的问题.

So, unfortunately, it seems that Kotlin's lambdas have the same problems as Java's Anonymous Inner Classes.

Java 规范 :

与类O的直接内部类C的实例i相关联 带有O的实例,称为O的直接封闭实例 一世.对象的直接封闭实例(如果有)是 确定何时创建对象

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

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

What this means is that the anonymous class will always have an implicit reference to the instance of the enclosing class. And since the reference is implicit there is no way to get rid of it.

看一个简单的例子

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();
   }
}

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

As you can see, in this case there will be the memory leak until the long-running task is executed. One workaround for this is to use static nested class.

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

Since Kotlin's non-inlined lambdas hold the reference to the instance of the enclosing class they have similar issues regarding memory leaks.

语法:

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

  • Declare SAM (single abstract method) interface

interface Runnable { void run(); }

  • 将此接口用作lambda的类型

  • Use this interface as a type for a lambda

    public void canTakeLambda(Runnable r) { ... }
    

  • 传递您的lambda

  • Pass your lambda

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

  • 内存泄漏问题:如

    对此的引用-包括通过 不合格的字段引用或方法调用-是, 本质上是对最终局部变量的引用. Lambda的身体 包含这样的引用捕获此的适当实例. 在 在其他情况下,对象不会保留对此内容的引用.

    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.

    Simply put, if you do not use any fields / methods from the enclosing class there is no implicit reference to this as in the case of anonymous classes.

    来自文档

    Lambda表达式通过将其转换为匿名来反向移植 内部阶级.这包括优化使用单个 无状态Lambda表达式的实例,以避免重复对象 分配.

    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.

    我想这是不言自明的.

    语法:

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

    • Declaration is similar to Kotlin, in Swift lambdas are called closures:

    func someFunctionThatTakesAClosure(closure: (String) -> Void) {}
    

  • 通过闭包

  • Pass the closure

    someFunctionThatTakesAClosure { print($0) }
    

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

    Here, $0 refer to the closure’s first String argument. This corresponds to it in Kotlin. Note: Unlike Kotlin, in Swift we can refer also to the other arguments like $1, $2 etc.

    内存泄漏问题:

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

    In Swift, just like in Java 8, the closure captures a strong reference to self (this in Java and Kotlin) only if it accesses a property of the instance, such as self.someProperty, or if the closure calls a method on the instance, such as self.someMethod().

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

    Also developers can easily specify that they want to capture only the weak reference:

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

    我希望Kotlin也有可能:)

    I wish it were possible in Kotlin too :)

    这篇关于Kotlin:安全的Lambda(无内存泄漏)吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆