8 minutes read

We have already learned how we can handle exceptions with the try-catch and try-catch-finally statements. Sometimes, we may need to work with different resources, like files, sockets, database connections, etc., and exceptions may occur in those cases, too. In this topic, we will explain how to handle exceptions while working with outside resources.

Working with resources

A resource is data that can be accessed by an application. Kotlin is a platform-independent programming language, and it lets you directly control only variables and objects but not computer memory and its objects, such as files, sockets, databases, etc. Kotlin does, however, provide methods to access operating system resources through the JVM.

For example, when we work with files (read file, write into file, etc.), an IO stream is created. The JVM notifies the operating system (OS) about starting the work with a file, and if everything is fine, the OS will return the file descriptor, which is used to access the file. In order to use input and output streams, you should import the following package:

import java.io.*

Let's look at the example below:

var reader = FileReader("somefile.txt")
val text = reader.readText()
reader.close()

Here, we create an instance of FileReader from the java.io package, which provides classes and their methods for reading and writing data. FileReader is used to read text data from the file identified by the path String. When we create an instance of FileReader, we open an input stream for reading data from a file.

If you want to write something into a file, you should use FileWriter; below you can see an example:

val writer = FileWriter("somefile.txt")
writer.write("hello!")
writer.close()

The close() method is used by the JVM to release resources. If we talk about files, the OS can provide a limited number of file descriptors. If we keep creating resources without releasing them, an exception may occur because there is not enough memory. That is why resources like FileReader, BufferedReader, Socket, etc. must be closed when they are not used any longer.

However, something can go wrong after declaring certain resources, and exceptions can be thrown. It means that close() isn't called and system resources are not released.

Get familiar with use()

Luckily, the above problem with resources can be solved by the use() method, which manages resources automatically. Let's modify our example :

val reader = FileReader("test.txt")
reader.use {
    reader.read()
}

The method takes a functional expression, executes it, and, by calling close() on it, releases resources every time the execution is completed – no matter with or without an exception.

We can use a shorter form with a lambda expression:

FileReader("test.txt").use { reader -> reader.read() }

Or otherwise, we can use the implicit variable it inside the block:

FileWriter("test.txt").use { it.write("something") }

The use() function refers only to JVM Kotlin, i.e., Kotlin code which compiles into JVM bytecode and can be run on the JVM.

Closeable vs AutoCloseable

Closeable and AutoCloseable interfaces introduce objects which may hold resources (such as file or socket handles) until the resource is closed. The function use() is defined in both interfaces, and after we invoke it, close() is called automatically when exiting a try-with-resources block. Closeable is an older interface, but it still extends AutoCloseable for the sake of backward compatibility.

The main difference between Closeable and AutoCloseable is that the former has a limited exception type – it can throw only IOException and its inheritors. Meanwhile, in AutoCloseable the exception range has been expanded to Exception.

We can invoke the use function on any object which implements AutoCloseable or Closeable, but AutoCloseable is newer and more flexible.

Let's try to implement the AutoCloseable interface and override its function close(). The implementation of Closeable will look similar.

class SomeResource : AutoCloseable {
    override fun close() {
        // close resource
    }
}

close() in the Closeable interface throws IOException, so it can't process other classes of exceptions. The close() function from AutoCloseable throws Exception, so, as you can see, the range of exceptions which can be processed by our class SomeResource is wider, which can be an argument in favor of using the AutoCloseable interface.

There are a lot of classes and interfaces which implement or extend the AutoCloseable interface, you can find them in the AutoCloseable documentation.

Conclusion

Resources associated with files, databases, and other information sources must be released after they finish their work; otherwise, we can exceed the memory limit. We've learned about try-with-resources constructions, which help us to handle exceptions from IO streams. Kotlin has its own concise and clear use function for automatically managing resources. Don't forget to use it while managing system resources. We've also learned about two interfaces: AutoCloseable and Closeable, which are used for releasing resources. Closeable is an older interface, and it still exists for the sake of backward compatibility, while AutoCloseable is more preferable due to its flexibility.

131 learners liked this piece of theory. 12 didn't like it. What about you?
Report a typo