try-with-resources / use / multiple resources

25

我正在使用一个大量使用了Autoclosable接口的Java API,因此在Java中使用try-with-resources。但是在Java中,您可以指定

try (res1, res2, res3...) {
  ...
}

我们有没有办法使用多个资源?这看起来像是众所周知的回调地狱:

val database = Databases.openDatabase(dbFile)

database.use {
  database.createResource(ResourceConfiguration.Builder(resPathName, config).build())

  val resMgr = database.getResourceManager(ResourceManagerConfiguration.Builder(resPathName).build())

  resMgr.use {
    val wtx = resMgr.beginNodeWriteTrx()

    wtx.use {
      wtx.insertSubtreeAsFirstChild(XMLShredder.createStringReader(resFileToStore))
    }
  }
}

据我所知,目前没有这样的功能,但是你可以编写自己的定制扩展函数,借鉴使用中的想法。 - AutonomousApps
4个回答

12

这方面目前没有标准解决方案。如果你在开始时就拥有了所有的可关闭实例,你可以使用自定义方法来处理它们,例如这篇博客文章或者这个存储库所展示的方式(并且这里是关于官方论坛上引领后者的讨论)。

然而,在你的情况下,由于后续对象依赖于先前的对象,像常规的 try-with-resources 一样使用这些方法都不适用。

我唯一能建议的是尝试为你自己定义辅助函数,隐藏嵌套的 use 调用,并立即将你置于资源获取的第二/第三/n层中,如果可能的话。


4
为了简便起见,我将使用 A、B 和 C 来代表链式自动关闭项。
import java.io.Closeable

open class MockCloseable: Closeable {
    override fun close() = TODO("Just for compilation")
}
class A: MockCloseable(){
    fun makeB(): B = TODO()
}
class B: MockCloseable(){
    fun makeC(): C = TODO()

}
class C: MockCloseable()

使用uses

这看起来像这样:

A().use {a ->
    a.makeB().use {b -> 
        b.makeC().use {c -> 
            println(c)
        }
    }
}

使用包装器来创建链式函数

定义

class ChainedCloseable<T: Closeable>(val payload: T, val parents: List<Closeable>) {
    fun <U> use(block: (T)->U): U {
        try {
            return block(payload)
        } finally {
            payload.close()
            parents.asReversed().forEach { it.close() }
        }
    }

    fun <U: Closeable> convert(block: (T)->U): ChainedCloseable<U> {
        val newPayload = block(payload)
        return ChainedCloseable(newPayload, parents + payload)
    }
}

fun <T: Closeable, U: Closeable> T.convert(block:(T)->U): ChainedCloseable<U> {
    val new = block(this)

}

使用方法

A()
    .convert(A::makeB)
    .convert(B::makeC)
    .use { c ->
         println(c)
    }

这样可以避免深度嵌套,但会创建包装对象。

1
作为 Kotlin 的忠实粉丝,这可能是我不喜欢它的主要原因之一。 - XZen

4
  • Method 1: For two resources and using native java resource manager:

    1. Define jUsing() in Kotlin:

      // crossinline version:
      inline fun <R, A : Closeable?, B : Closeable?>
              jUsing(a: A, b: B, crossinline block: (A, B) -> R): R = 
          J.jUsing(a, b) { c, d -> block(c, d) }
      
    2. And also Util.jUsing() in Util.java:

      Note: Below code is compatible with Java 9+. You can implement it with try-catch-finally to make it compatible with previous versions. See here for an example.

      public static <R, A extends AutoCloseable, B extends AutoCloseable> R 
      jUsing(A a, B b, Function2<A, B, R> block) throws Exception {
          try (a; b) {
              return block.invoke(a, b);
          }
      }
      

      (Function2 is kotlin.jvm.functions.Function2.)

    3. Then use like below:

      // Download url to destFile and close streams correctly:
      jUsing(URL(url).openStream(), FileOutputStream(destFile), InputStream::transferTo)
      

      Note: Above code used Java 9+ InputStream.transferTo() method. See here for a transferTo() Kotlin alternative that is compatible with previous versions.


    Note: You can write Kotlin jUsing() method more simple using noinline keyword instead of crossinline. But I think crossinline version has more performance:

    // noinline version:
    inline fun <R, A : Closeable?, B : Closeable?>
            jUsing(a: A, b: B, noinline block: (A, B) -> R): R =
            Util.jUsing(a, b, block)
    

  • Method 2: For two resources (and with similar usage to method 1):

    Thank @zsmb13's answer for the link

    /**
     * Based on https://github.com/FelixEngl/KotlinUsings/blob/master/Usings.kt
     * and with some changes
     */
    inline fun <R, A : Closeable, B : Closeable> using(a: A, b: B, block: (A, B) -> R): R {
        var exception: Throwable? = null
    
        try {
            return block(a, b)
        } catch (e: Throwable) {
            exception = e
            throw e
        } finally {
            if (exception == null) {
                a.close()
                b.close()
            } else {
                try {
                    a.close()
                } catch (closeException: Throwable) {
                    exception.addSuppressed(closeException)
                }
                try {
                    b.close()
                } catch (closeException: Throwable) {
                    exception.addSuppressed(closeException)
                }
            }
        }
    }
    

  • Method 3: For any number of resources (arrayOf(stream1, stream2, ...).use {...}):

    /**
     * Based on https://medium.com/@appmattus/effective-kotlin-item-9-prefer-try-with-resources-to-try-finally-aec8c202c30a
     * and with a few changes
     */
    inline fun <T : Closeable?, R> Array<T>.use(block: (Array<T>) -> R): R {
        var exception: Throwable? = null
    
        try {
            return block(this)
        } catch (e: Throwable) {
            exception = e
            throw e
        } finally {
            when (exception) {
                null -> forEach { it?.close() }
                else -> forEach {
                    try {
                        it?.close()
                    } catch (closeException: Throwable) {
                        exception.addSuppressed(closeException)
                    }
                }
            }
        }
    }
    

    See referenced link for more details.


3

这是另一种方法:


val CloseableContext = ThreadLocal<MutableList<AutoCloseable>>()

inline fun scopeDef(inScope: () -> Unit) {
    val oldContext = CloseableContext.get()

    val currentContext = mutableListOf<AutoCloseable>()

    CloseableContext.set(currentContext)

    try {
        inScope()
    }
    finally {
        for(i in (currentContext.size - 1) downTo 0) {
            try {
                currentContext[i].close()
            }
            catch(e: Exception) {
                // TODO: Record as suppressed exception
            }
        }
        CloseableContext.set(oldContext)
    }
}

fun <T: AutoCloseable> autoClose(resource: T): T {
    CloseableContext.get()?.add(resource) ?: throw IllegalStateException(
            "Calling autoClose outside of scopeDef is forbidden")

    return resource
}

使用方法:

class Close1(val name: String): AutoCloseable {
    override fun close() {
        println("close $name")
    }
}

fun main(args : Array<String>) {
    scopeDef {
        val c1 = autoClose(Close1("1"))

        scopeDef {
            val c3 = autoClose(Close1("3"))
        }

        val c2 = autoClose(Close1(c1.name + "+1"))

    }
}

输出:

close 3
close 1+1
close 1

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