【问题标题】:try-with-resources / use / multiple resourcestry-with-resources / 使用 / 多个资源
【发布时间】:2018-04-20 14:19:12
【问题描述】:

我正在使用一个 Java-API,它大量使用 Autoclosable-Interface,因此在 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))
    }
  }
}

【问题讨论】:

  • 我不知道,但没有什么能阻止你编写自己的自定义扩展函数,借用使用中的想法。

标签: kotlin


【解决方案1】:

对此没有标准解决方案。如果你一开始就准备好所有的Closable 实例,你可以使用自己定义的方法来处理它们,比如this blog postthis repository 显示(而here 是官方论坛上的讨论导致后者)。

但是,在您的情况下,如果后续对象依赖于之前的对象,这些对象都不会像常规的 try-with-resources 那样适用。

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

【讨论】:

    【解决方案2】:

    为简单起见,我将使用 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()
    

    使用用途

    看起来像这样:

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

    这使您可以避免以创建包装对象为代价的深度嵌套。

    【讨论】:

      【解决方案3】:
      • 方法一:针对两个资源并使用原生java资源管理器

        1. 在 Kotlin 中定义 jUsing()

          // 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. 还有Util.jUsing()Util.java

          注意:code 下方与 Java 9+ 兼容。您可以使用try-catch-finally 实现它,以使其与以前的版本兼容。示例见here

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

          Function2kotlin.jvm.functions.Function2。)

        3. 然后像下面这样使用:

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

          注意:以上code 使用了Java 9+ InputStream.transferTo() 方法。请参阅 here 以获取与以前版本兼容的 transferTo() Kotlin 替代方案。


        注意:您可以使用noinline 关键字而不是crossinline 更简单地编写Kotlin jUsing() 方法。但是我觉得crossinline版本的性能更好:

        // 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)
        

      • 方法2:对于两个资源(与方法1的用法相似)

        感谢@zsmb13's answerthe 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)
                    }
                }
            }
        }
        

      • 方法3:任意数量的资源(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)
                        }
                    }
                }
            }
        }
        

        更多详情请参阅referenced link

      【讨论】:

        【解决方案4】:

        另一种方法:

        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
        

        【讨论】:

          猜你喜欢
          • 2015-08-13
          • 2013-09-12
          • 2011-10-16
          • 2020-01-01
          • 1970-01-01
          • 2016-09-29
          • 2017-11-18
          • 2020-08-10
          • 1970-01-01
          相关资源
          最近更新 更多