Попробуй с ресурсами / используй / несколько ресурсов

Я использую Java-API, который интенсивно использует Autoclosable-Interface и, следовательно, в Java try-with-resources. Однако в Java вы можете указать

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

У нас есть способ использовать более одного ресурса? Похоже на хорошо известный callback-ад:

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

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

Использование использует

Это будет выглядеть так:

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

Это позволяет избежать глубокого вложения за счет создания объектов-оболочек.

Для этого не существует стандартного решения. Если бы у вас было все Closable экземпляры, готовые в начале, вы можете использовать свои собственные определяемые методы для их обработки, как показано в этом сообщении в блоге или в этом репозиториивот обсуждение на официальных форумах, которое привело к последнему).

Однако в вашем случае, когда последующие объекты опираются на предыдущие, ни один из них не применяется как обычный try-with-resources было бы.

Единственное, что я могу предложить, это попытаться определить вспомогательные функции для себя, которые скрывают вложенные use звонки, и немедленно поместите вас во второй / третий / n-й слой этих приобретений ресурсов, если это вообще возможно.

  • Метод 1 (для двух ресурсов): Использование собственного менеджера ресурсов Java:

    определять 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) }
    

    А также Util.jUsing() в Util.java :

    Примечание: ниже code совместим с Java 9+. Вы можете реализовать это с try-catch-finally сделать его совместимым с предыдущими версиями. Смотрите здесь для примера.

    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() является kotlin.jvm.functions.Function2 .)

    Затем используйте как ниже:

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

    Примечание: выше code используется Java 9+ InputStream.transferTo() метод. Смотрите здесь для transferTo() Альтернатива Kotlin, совместимая с предыдущими версиями.


    Примечание: Вы можете написать Котлин jUsing() метод более простой, используя noinline ключевое слово вместо crossinline, Но я думаю 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 ответ за ссылку

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

    Смотрите ссылку ссылку для более подробной информации.

Еще один подход к этому:

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
Другие вопросы по тегам