Попробуй с ресурсами / используй / несколько ресурсов
Я использую 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