Kotlin中等同于Swift的defer关键字

7

在Kotlin中是否有类似于Swift关键字“defer”的功能?

“defer”关键字的作用是确保在函数返回之前执行defer块中的代码。

以下是一个假设在Kotlin中存在defer关键字的示例。

    class MyClass {

        var timeStamp = 0L

        fun isEdible(fruit: Fruit): Boolean {
          defer { 
           timeStamp = System.currentTimeMillis()
          }
          if (fruit.isExpired) {
             return false
          }
          if (fruit.isRipe) {
            return true
          }
          return false
        }
     }

在上述情况下,无论函数在何时返回,defer块内的代码都会被执行,并且时间戳的值将在函数结束前得到更新。
我知道在Java中,finally {}关键字与try{} catch{}一起使用,但这并不完全是defer所提供的。

1
我不使用 Swift,但是延迟函数是一种异步线程,会在方法返回之前与主线程“合并”吗? - Pawel
3
在Swift中,defer仅仅是在离开当前作用域(例如退出函数)后执行defer块。 - dfrib
4
try/finally,我认为注:try/finally是一种程序设计语言结构,在程序中通常用于确保无论发生什么情况,某些代码都会被执行或资源将被释放。 - Alexander
1
我赞同@Alexander的观点:快速查看Kotlin语法确实会指向finally块。我记得很久以前读过一篇关于Swift的defer的文章,将其描述为“正确使用tryfinally”。因此,我相信你可能得到的答案并不是你要找的(至少不是Kotlin本身)。 - dfrib
@rgv 你的问题得到解答了吗? - Alexander
5个回答

6

在Kotlin中没有这样的关键字,但是您可以自己构建一个相似的结构。类似于下面的代码(请注意,此代码不处理延迟块中的异常):

class Deferrable {
    private val actions: MutableList<() -> Unit> = mutableListOf()

    fun defer(f: () -> Unit) {
        actions.add(f)
    }

    fun execute() {
        actions.forEach { it() }
    }
}

fun <T> defer(f: (Deferrable) -> T): T {
    val deferrable = Deferrable()
    try {
        return f(deferrable)
    } finally {
        deferrable.execute()
    }
}

然后您可以像这样使用它:
class MyClass {

    var timeStamp = 0L

    fun isEdible(fruit: Fruit): Boolean = defer { d ->
      d.defer { 
       timeStamp = System.currentTimeMillis()
      }
      if (fruit.isExpired) {
         return false
      }
      if (fruit.isRipe) {
        return true
      }
      return false
    }
}

有趣的做法!Try/Finally 明显是更合适的选择,但这个方法很巧妙。 - Alexander
@AlexeyRomanov 我考虑过这个问题,但我决定不想将 this 的值从 MyClass 更改为 Deferrable - marstran
1
好的,只是提供作为一个选项。 - Alexey Romanov
你可能想把 actionsMutableList 改为 Stack - mvndaai
@marstran 在 Swift 中的 defer 块中不能使用任何返回语句(例如 breakreturnthrow 等)。 - Cuneyt
显示剩余5条评论

4
最接近的等价物是try/finally。如果没有抛出异常,catch不是必需的。
try {
    println("do something")
    // ... the rest of your method body here
}
finally {
    println("Don't forget about me!");
}

在Swift中,defer通常用于确保您不会忘记清理某种资源(文件句柄、数据库连接、共享内存映射等)。为此,Kotlin使用with方法,它接受一个闭包作为参数,将资源传递给该闭包。在闭包的生命周期内,资源是有效的,并且在结束时自动关闭。

FileWriter("test.txt")
  .use { it.write("something") }
// File is closed by now

1

通过异常处理的解决方案:

class DeferContext {
    private val list = mutableListOf<() -> Unit>()

    fun defer(payload: () -> Unit) {
        list += payload
    }

    /** lombok `@Cleanup` analog */
    fun AutoCloseable.deferClose() = apply {
        defer { close() }
    }

    fun executeDeferred(blockError: Throwable?) {
        var error: Throwable? = blockError
        for (element in list.reversed()) {
            try {
                element()
            } catch (e: Throwable) {
                if (error == null) {
                    error = e
                } else {
                    error.addSuppressed(e)
                }
            }
        }
        error?.let { throw it }
    }
}

inline fun <T> deferBlock(payload: DeferContext.() -> T): T {
    val context = DeferContext()
    var error: Throwable? = null
    var result: T? = null
    try {
        result = context.payload()
    } catch (e: Throwable) {
        error = e
    } finally {
        context.executeDeferred(error)
    }
    return result as T
}

在我看来,defer 功能的主要作用是无论之前是否有异常抛出,都会执行延迟操作。

用法:

deferBlock {
    defer { println("block exited") }
    val stream = FileInputStream("/tmp/a").deferClose()
}

0
今天我遇到了同样的问题。
虽然我认为marstran提供的答案很好,但我决定稍微重构一下。
fun <T> deferred(f: ((() -> Unit) -> Unit) -> T): T {
    val actions: MutableList<() -> Unit> = mutableListOf()
    try {
        return f(actions::add)
    } finally {
        actions.asReversed().forEach { it() }
    }
}

我通过直接在deffered函数中使用列表来摆脱Deferrable 类。这也解决了整个Deferrable对象被传递到需要调用it.defer/d.defer的调用代码的问题。在此版本中,可变列表的add方法直接传递到lambda中,使得代码更接近于其go/swift版本。
为了解决mvndaai建议使用Stack的建议,我决定在列表上调用.asReversed()。也许在kotlin中有一种与非JVM变体中也可用的LI-FO类型,但如果没有,我认为这是一个好的解决方案。
给定示例将如下所示:
class MyClass {

    var timeStamp = 0L

    fun isEdible(fruit: Fruit): Boolean = deferred { defer ->
      defer { 
       timeStamp = System.currentTimeMillis()
      }
      if (fruit.isExpired) {
         return false
      }
      if (fruit.isRipe) {
        return true
      }
      return false
    }
}

0
如果类是Closeable,您可以使用use block
class MyClass : Closeable {
        var timeStamp = 0L

        override fun close() {
            timeStamp = System.currentTimeMillis()
        }

        fun test(): Boolean {
            this.use {
                if (fruit.isExpired) {
                    return false
                }
                if (fruit.isRipe) {
                    return true
                }
                return false
            }
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接