Chapter 10. Input/Output

Kotlin makes doing input/output (I/O) operations easy, but the style is different from what a Java developer may expect. Resources in Kotlin are frequently closed by employing a use function that does so on the user’s behalf. This chapter includes a couple of recipes that focus on that approach, specifically for files, but that can be generalized to other resources.

10.1 Managing Resources with use

Problem

You want to process a resource such as a file and be sure that it is closed when you are finished, but Kotlin does not support Java’s try-with-resources construct.

Solution

Use the extension functions use or useLines on readers or input/output streams.

Discussion

Java 1.7 introduced the try-with-resources construct, which allows a developer to open a resource inside parentheses between the try keyword and its corresponding block; the JVM will automatically close the resource when the try block ends. The only requirement is that the resource come from a class that implements the Closeable interface. File, Stream, and many other classes implement Closeable, as the example in Example 10-1 shows.

Example 10-1. Using try-with-resources from Java
package io;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class TryWithResourcesDemo {
    public static void main(String[] args) throws IOException {
        String path = "src/main/resources/book_data.csv";

        File file = new File(path);
        String line = null;
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {  1
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }

        try (Stream<String> lines = Files.lines(Paths.get(path))) {  2
            lines.forEach(System.out::println);
        }
    }
}
1

BufferedReader implements Closeable

2

Stream implements Closeable

Both the BufferedReader class and Stream interface implement Closeable, so each has a close method that is automatically invoked when the try block completes.

A few interesting features are worth noting:

  • In Java 10 or above, the declaration of BufferedReader and Stream could be replaced by the reserved word var. In fact, this is one of the primary use cases for local variable type inference. It is not used here to avoid confusion with Kotlin’s var keyword.

  • In Java 9 or above, you no longer have to create the Closeable variable inside the parentheses. It can be supplied from outside. Again, there is no benefit to doing so here.

  • Since the main method signature was modified to throw IOException, the result is one of those rare moments when you have a try block without either a catch or a finally.

This is all well and good, but unfortunately the try-with-resources construct is not supported by Kotlin. Instead, Kotlin adds the extension functions use to Closeable and useLines to Reader and File.

The signature of useLines is given by the following:

inline fun <T> File.useLines(
    charset: Charset = Charsets.UTF_8,
    block: (Sequence<String>) -> T
): T

The optional first argument is a character set, which defaults to UTF-8. The second argument is a lambda that maps a Sequence of lines from the file into a generic argument, T. The implementation of the useLines function automatically closes the reader after the processing is complete.

As an example, on Unix systems ultimately based on BSD (which include macOS), there is a file containing all the words from Webster’s Second International Dictionary, which is out of copyright. On a Mac, the file is located in the directory /usr/share/dict/words, and contains 238,000 words, one per line. Example 10-2 returns the 10 longest words in the dictionary.

Example 10-2. Finding the 10 longest words in the dictionary
fun get10LongestWordsInDictionary() =
    File("/usr/share/dict/words").useLines { line ->
        line.filter { it.length > 20 }
            .sortedByDescending(String::length)
            .take(10)
            .toList()
    }

The function filters out any line shorter than 20 characters (and since each line has a single word, that’s any word shorter than 20 characters), sorts them by length in descending order, selects the first 10, and returns them in a list.

To call this function, use the following:

get10LongestWordsInDictionary().forEach { word ->
    println("$word (${word.length})")

This prints the following:

formaldehydesulphoxylate (24)
pathologicopsychological (24)
scientificophilosophical (24)
tetraiodophenolphthalein (24)
thyroparathyroidectomize (24)
anthropomorphologically (23)
blepharosphincterectomy (23)
epididymodeferentectomy (23)
formaldehydesulphoxylic (23)
gastroenteroanastomosis (23)

The implementation of File.useLines in the standard library is given by Example 10-3.

Example 10-3. Implementation of useLines as an extension function on File
inline fun <T> Reader.useLines(
    block: (Sequence<String>) -> T): T =
    buffered().use { block(it.lineSequence()) }

Note that the implementation creates a buffered reader (returned by the buffered function) and delegates to its use function.

The corresponding signature of the use function is given by Example 10-4.

Example 10-4. Signature of the use extension function on Closeable
inline fun <T : Closeable?, R> T.use(block: (T) -> R): R

The implementation is complicated by the need for exception handling, but at its base it is similar to this:

try {
    return block(this)
} catch (e: Throwable) {
    // save the exception to be used later
    throw e
} finally {
    close() // requires another try/catch block
}

The use block is an example of the Execute Around Method design pattern, where the infrastructure code is built into the library, and a provided lambda does the actual work. This separation of infrastructure from business logic makes it easier to focus on the task at hand.

Note

The use function in this recipe is defined on Closeable, which is available in Java 6. If you require the underlying JDK to support Java 8, the same function is defined in AutoCloseable as well.

See Also

Recipe 10.2 shows how to code with the use block directly. The use function is also employed to shut down a Java thread pool in Recipe 13.4.

10.2 Writing to a File

Problem

You want to write to a file.

Solution

In addition to the normal Java input/output (I/O) methods, extension functions to the File class return output streams and writers.

Discussion

Several extension functions have been added to Java’s java.io.File class. You can iterate through a file by using the forEachLine function. You can call readLines on a file to get a collection containing all the lines in the file, which is useful if the file is not very large. The useLines function, described in Recipe 10.1, allows you to supply a function that will be invoked on each line. If the file is small enough, you can use readText or readBytes to read the entire contents into a string or a byte array, respectively.

If you want to write to a file and replace all of its existing contents, use the writeText function, as in Example 10-5.

Example 10-5. Replacing all the text in a file
File("myfile.txt").writeText("My data")

It’s hard to be much simpler than that. The writeText function takes an optional parameter to represent the character set, whose default value is UTF-8.

The File class also has an extension function called appendText, which can add data to a given file.

The writeText and appendText functions delegate to writeBytes and appendBytes, each of which takes advantage of the use function to ensure that the file is closed when writing is finished.

You can also use the writer (or printWriter) and bufferedWriter functions, which return an OutputStreamWriter and a BufferedWriter, as you might expect. With either of them, you can add a use block to do the actual writing, as in Example 10-6.

Example 10-6. Writing with the use function
File(fileName).printWriter().use { writer ->
    writer.println(data) }

Using bufferedWriter with a use block works exactly the same way.

See Also

Recipe 10.1 goes into detail about the use and useLines functions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.9.169