try-with-resources / use / multiple resources

前端 未结 4 1787
臣服心动
臣服心动 2021-02-20 01:07

I\'m using a Java-API which heavily uses the Autoclosable-Interface and thus in Java try-with-resources. However in Java you can specify



        
相关标签:
4条回答
  • 2021-02-20 01:45

    There is no standard solution for this. If you had all of the Closable instances ready at the start, you could use your own self-defined methods to handle them, like this blog post or this repository shows (and here is the discussion on the official forums that led to the latter).

    In your case however, where subsequent objects rely on the previous ones, none of these apply like a regular try-with-resources would.

    The only thing I can suggest is trying to define helper functions for yourself that hide the nested use calls, and immediately place you in the second/third/nth layer of these resourcs acquisitions, if that's at all possible.

    0 讨论(0)
  • 2021-02-20 01:48

    For simplicity I will use A,B and C for the chained autocloseables.

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

    Using uses

    This would look like this:

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

    Making a chain use function with a wrapper

    Definition

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

    Usage

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

    This allows you to avoid having to nest deeply, at the cost of creating wrapper objects.

    0 讨论(0)
  • 2021-02-20 01:48
    • 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.

    0 讨论(0)
  • 2021-02-20 01:51

    Yet another approach for this:

    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
    }
    

    Usage:

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

    Output:

    close 3
    close 1+1
    close 1
    
    0 讨论(0)
提交回复
热议问题