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.
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