We have already mentioned that input streams should be closed after they have been used. Let's discuss what happens when you're working with outside resources: how closing can be performed and why it is important.
Why close?
When an input stream is created, the JVM notifies the OS about its intention to work with a file. If the JVM process has enough permissions and everything is fine, the OS returns a file descriptor — a special indicator used by a process to access the file. The problem is that the number of file descriptors is limited. This is the reason why it is important to notify the OS that the job is done and the file descriptor that is held can be released for further reuse. In previous examples, we invoked the method close for this purpose. Once it is called, the JVM releases all system resources associated with the stream.
Pitfalls
Resource releasing works if the JVM calls the close method, but it is possible that the method will not be called at all.
Look at the example:
val reader: Reader = FileReader("file.txt")
// code which may throw an exception
reader.close()
Suppose something goes wrong before the close invocation and an exception is thrown. It leads to a situation in which the method will never be called and system resources won't be released. It is possible to solve the problem by using the try-catch-finally construction:
var reader: Reader? = null
try {
reader = FileReader("file.txt")
// code which may throw an exception
} finally {
reader!!.close()
}
In this and the following examples, we assume that file.txt exists and do not check the instance of Reader for null in the finally block. We do it to keep the code snippet as simple as possible, but it is not safe in the case of a real application.
Thrown exceptions cannot affect the invocation of the close method now.
Unfortunately, this solution still has some problems. That is, the close method can potentially raise exceptions itself. Suppose there are two exceptions: the first was raised inside the try section, and the second was thrown by the finally section. It leads to the loss of the first exception. Let's see why this happens:
@Throws(IOException::class)
fun readFile() {
var reader: Reader? = null
try {
reader = FileReader("file.txt")
throw RuntimeException("Exception1")
} finally {
reader?.close() // throws new RuntimeException("Exception2")
}
}
First, the try block throws an exception. As we know, the finally block is invoked anyway. Next, in our example, the close method throws an exception. When two exceptions occur, which one is thrown outside the method? It will be the latter one: Exception2 in our case. It means we will never know that the try block raised an exception at all.
Let's try to reason and fix this. Ok, we don't want to lose the first exception, so we upgrade the code a little bit and handle Exception2 right after it was thrown:
@Throws(IOException::class)
fun readFile() {
var reader: Reader? = null
try {
reader = FileReader("file.txt")
throw RuntimeException("Exception1")
} finally {
try {
reader!!.close() // throws new RuntimeException("Exception2")
} catch (e: Exception) {
// handle Exception2
}
}
}
Now, the piece of code throws Exception1 outside. It may be correct, but we still do not save information on both exceptions, and sometimes we don't want to lose it. So now, let's see how we can handle this situation nicely.
Solution
A simple and reliable way called try-with-resources was introduced in Java 7. In Kotlin, there is no implementation of try-with-resources, but we can apply the use extension method:
FileReader("file.txt").use {
reader ->
// some code
}
Example in Java:
try (Reader reader = new FileReader("file.txt")) {
// some code
}
It is possible to create several objects as well. The code below is also fine:
FileReader("file1.txt").use { reader1 ->
FileReader("file2.txt").use { reader2 ->
// some code
}
}
The second part just contains some code for dealing with the object created in the first part.
As you see, there are no explicit calls of the close method at all. It is implicitly invoked for all objects declared in the first part. The construction guarantees closing all resources in a proper way.
You can initialize the input stream outside the design and then utilize the use method:
val reader: Reader = FileReader("file.txt")
reader.use {
// some code
}
Surely, we do our best to write error-free programs. However, it is difficult to foresee all possible problems. The best practice is to place any code concerning system resources in the use method.
Closeable resources
We have dealt with a file input stream to demonstrate how try-with-resources is used. However, not only the resources based on files should be released. Closing is also crucial for other outside sources, like web or database connections. Classes that handle them have a close method and, therefore, can be wrapped by the try-with-resources statement.
For example, let's consider java.util.Scanner. Earlier, we used Scanner for reading data from the standard input, but it can read data from a file as well. Scanner has a close method for releasing outside sources.
Let's consider an example of a program that reads two integers separated by a space from a file and prints them:
Scanner(File("file.txt")).use { scanner ->
val first: Int = scanner.nextInt()
val second: Int = scanner.nextInt()
println("arguments: $first $second")
}
Suppose something went wrong and the file content is 123 not_number, where the second argument is a String. It leads to a java.util.InputMismatchException while parsing the second argument. The use method guarantees that file-related resources are released properly.
What's under the hood?
The use implementation looks like this:
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
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
}
}
}
}
This function is defined as a generic extension on all Closeable? types. Closeable is Java's interface that uses try-with-resources.
The function takes a function literal block, which gets executed in try. Same as with try-with-resources in Java, Closeable gets closed in finally. Also, failures happening inside block lead to close executions, where possible exceptions are literally "suppressed" by just being ignored. This is different from try-with-resources because such exceptions can be requested in Java‘s solution.
If Kotlin didn't implement the use extension functions, we would need to write more code, take a look:
import java.io.Closeable
fun main() {
val resource = CustomCloseable()
resource.use {
println("I am an example of some code!")
it.exampleMethod()
}
println("End of program!")
}
class CustomCloseable : Closeable {
override fun close() {
println("Close resource")
}
fun exampleMethod() {
println("Example of some method for the CustomCloseable class")
}
}
And look at the output in the console:
Let's analyze what is happening here:
- the CustomCloseable class implements a single close() method of the Closeable interface;
- in the CustomCloseable class, as an example, we added the exampleMethod() method, which prints the line "Example of some method for the CustomCloseable class";
- in the resource object of the CustomCloseable class, we call the extension function use:
resource.use {
println("I am an example of some code!")
it.exampleMethod()
}
In the console, we see a printed line "Close resource"; this line is printed when the close() method is called, but we did not call it in our code. This is the magic of using the extension function use, which under the hood implements a call to this function, which leads to the closure and release of the resources used. To fully understand what is happening, let's look at the interface code Closeable:
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
As you can see, this interface expands the AutoCloseable interface. This is another interface that implements the close() method. To understand the difference between these interfaces, let's look at the AutoCloseable code:
public interface AutoCloseable {
void close() throws Exception;
}
The difference between these interfaces is that they generate different exceptions. AutoCloseable generates Exception, while Closeable generates IOException. Thus, we can say that Closeable can be used in the code where I/O errors may be thrown. AutoCloseable is more flexible and can be used in situations where different types of exceptions may occur.
Conclusion
Inappropriate resource handling may lead to serious problems. Resources associated with files, the web, databases, or other outside sources should be released after use. Standard library classes dealing with outside sources have a close method for that purpose. Sometimes, releasing resources in a proper way may get complicated. Kotlin introduced the use method, which does all the work for you. Do not forget to use it when you're dealing with system resources.