Joao Alves

Engineering Manager at Skyscanner

Kotlin try-with-resources — use

Posted at — Apr 26, 2018

image

Hi there everyone, welcome to number 11 in the Kotlin Playground Series. We’ll continue with standard functions, today will look at just one so it shouldn’t get too long.

We’re going to look into how to manage resources in Kotlin or in other words more common, how to handle try-with-resources. Let’s look at a couple of Java examples first to see what I’m talking about:

private String readFirstLine() throws FileNotFoundException {
    BufferedReader reader = new BufferedReader(new FileReader("test.file"));
    try {
        return reader.readLine();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

That looks familiar, right? I’m sure you had to write something similar to this at some point and you would agree it’s a bit of a boring task to write all that boilerplate. And worse than that, how many times did you forget to call that close() function in the finally block?

Well, as usual, Kotlin to the rescue. In Kotlin there’s a standard function that simplifies all of that boilerplate code and lets us focus on the important bit, the readLine() function in our case. Let’s take a look at it:

private fun readFirstLine(): String {
    BufferedReader(FileReader("test.file")).use { return it.readLine() }
}

And that’s how you use use() 👀. It’s that simple, one line of code and you’re done. No need to worry about closing the BufferedReader as both that and all the exception handling is taken care for us.

How it works

inline fun <T : Closeable?, R> T.use(block: (T) -> R): R

So how does this work you ask. The use() function as you can see above, it’s an extension function on the Java Closeable interface. This means we can invoke it in any object that implements the Closeable or AutoCloseable interfaces. BufferedReader is as we know part of this group.

There are some cases of classes that implement AutoCloseable but not Closeable , and because Kotlin standard library targets Java 6, and AutoCloseable was only introduced in Java 7, for those, we need to add a dependency on Kotlin extensions for Java 7 or above depending on what version we’re targeting. We can do that by adding the following line in our dependencies:

implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.40'

And now we get a new signature for the use() function that we can invoke on objects that implement the AutoCloseable interface but not the Closeable one:

inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R

If we look at the implementation of use() we can see exactly how it works and how it allows us to just focus on the readLine() piece:

/**
 * Executes the given [block] function on this resource and then closes it down correctly whether an exception
 * is thrown or not.
 *
 * @param block a function to process this [Closeable] resource.
 * @return the result of [block] function invoked on this resource.
 */
@InlineOnly
@RequireKotlin("1.2", versionKind = RequireKotlinVersionKind.COMPILER_VERSION, message = "Requires newer compiler version to be inlined correctly.")
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

/**
 * Closes this [Closeable], suppressing possible exception or error thrown by [Closeable.close] function when
 * it's being closed due to some other [cause] exception occurred.
 *
 * The suppressed exception is added to the list of suppressed exceptions of [cause] exception, when it's supported.
 */
@SinceKotlin("1.1")
@PublishedApi
internal fun Closeable?.closeFinally(cause: Throwable?) = when {
    this == null -> {}
    cause == null -> close()
    else ->
        try {
            close()
        } catch (closeException: Throwable) {
            cause.addSuppressed(closeException)
        }
}

As you can see above, all is handled for us. use() returns the result of the function we pass as an argument, in our case readLine() and then takes care of closing the BufferedReader by calling close() on the calling resource. It also handles all possible exceptions including possible exception while closing the resource.

Note: As suggested by Simon in the comments, it’s worth mentioning that from Java 7 onwards we can also use try-with-resources and get resources automatically closed, since they also implement AutoCloseable from that version. This is how it would look:

private String readFirstLineJava7(String path) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader("test.file"))) {
        return reader.readLine();
    }
}

And that’s it for today, a small one like I said. Use use() 👀 to save some more boilerplate code and be assured your resources will be properly closed no matter what. If you enjoyed the article don’t forget to 👏 and please share your ideas and comments as usual. The code is available under the trywithresources package on the series project. See you at number 12 👋

Kotlin runtime checks — require and check ⇦ PREVIOUS

NEXT ⇨ Kotlin Playground — finish line


comments powered by Disqus