try-with-resources: "use" extension function in Kotlin does not always work
Asked Answered
C

4

16

I had some trouble expressing the Java's try-with-resources construct in Kotlin. In my understanding, every expression that is an instance of AutoClosable should provide the use extension function.

Here is a complete example:

import java.io.BufferedReader;
import java.io.FileReader;

import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;

public class Test {

    static String foo(String path) throws Throwable {
        try (BufferedReader r =
           new BufferedReader(new FileReader(path))) {
          return "";
        }
    }

    static String bar(TupleQuery query) throws Throwable {
        try (TupleQueryResult r = query.evaluate()) {
          return "";
        }
    }
}

The Java-to-Kotlin converter creates this output:

import java.io.BufferedReader
import java.io.FileReader

import org.openrdf.query.TupleQuery
import org.openrdf.query.TupleQueryResult

object Test {

    @Throws(Throwable::class)
    internal fun foo(path: String): String {
        BufferedReader(FileReader(path)).use { r -> return "" }
    }

    @Throws(Throwable::class)
    internal fun bar(query: TupleQuery): String {
        query.evaluate().use { r -> return "" } // ERROR
    }
}

foo works fine, but the code in bar does not compile:

Error:(16, 26) Kotlin: Unresolved reference.
None of the following candidates is applicable
because of receiver type mismatch: 
public inline fun <T : java.io.Closeable, R>
???.use(block: (???) -> ???): ??? defined in kotlin.io

query.evaluate() is from Sesame and implements AutoClosable. Is it a Kotlin bug, or is there a reason why it does not work?


I am using IDEA 15.0.3 with Kotlin 1.0.0-beta-4584-IJ143-12 and the following sasame-runtime version:

<groupId>org.openrdf.sesame</groupId>
<artifactId>sesame-runtime</artifactId>
<version>4.0.2</version>
Cool answered 1/2, 2016 at 17:31 Comment(0)
P
22

Kotlin targets Java 6 at the moment, so its standard library does not use the AutoCloseable interface. The use function only supports the Java 6 Closeable interface. See the issue tracker for reference.

You can create a copy of the use function in your project and modify it to replace Closeable with AutoCloseable:

public inline fun <T : AutoCloseable, R> T.use(block: (T) -> R): R {
    var closed = false
    try {
        return block(this)
    } catch (e: Exception) {
        closed = true
        try {
            close()
        } catch (closeException: Exception) {
            e.addSuppressed(closeException)
        }
        throw e
    } finally {
        if (!closed) {
            close()
        }
    }
}
Proudhon answered 1/2, 2016 at 18:21 Comment(2)
As mfulton26 pointed out in his comment kotlin.Throwable doesn't contain addSuppressed(Throwable) method so we have to cast kotlin.Throwable to java.lang.Throwable to make the code work. https://mcmap.net/q/748270/-my-own-solution-for-kotlin-39-s-try-with-resources-absenceNeveda
i believe this works @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (e as java.lang.Throwable).addSuppressed(closeException)Cassey
R
12

Kotlin 1.1+ has a standard library that targets Java 8 to support Closeable resource pattern - kotlin-stdlib-jre8

Gradle

compile "org.jetbrains.kotlin:kotlin-stdlib:1.1.1"
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:1.1.1"

Maven

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>1.1.1</version>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jre8</artifactId>
    <version>1.1.1</version>
</dependency>

Sample

val resource: AutoCloseable = getCloseableResource() 
resource.use { r -> //play with r }
Resound answered 7/4, 2017 at 4:42 Comment(1)
kotlin-stdlib-jre8 is now deprecated, replaced with kotlin-stdlib-jdk8. Furthermore, AutoCloseable was introduced in Java 7, so a jdk7 version is enough. As per why these libraries must be included, Kotlin by default targets Java 6.Monologue
H
1

For classes that do not support the "use" function, I have done the next homemade try-with-resources:

inline fun <T:AutoCloseable,R> trywr(closeable: T, block: (T) -> R): R {
    try {
        return block(closeable);
    } finally {
        closeable.close()
    }
}

Then you can use it the next way:

fun countEvents(sc: EventSearchCriteria?): Long {
    return trywr(connection.prepareStatement("SELECT COUNT(*) FROM event")) {
        var rs = it.executeQuery()
        rs.next()
        rs.getLong(1)
    }
}
Hemihedral answered 28/3, 2016 at 10:32 Comment(0)
A
1

Just make sure kotlin-stdlib-jdk7.jar in classpath, it's not added by default

Aloisius answered 16/4, 2020 at 11:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.