Lesson 12 of 13

Files, I/O & Modern Java Features

Learn to read and write files with java.nio.file, and use newer language features such as records, sealed types and pattern matching to keep code short.

Java
import java.nio.file.*;
import java.io.*;
import java.util.*;
import java.util.stream.*;

Path path = Path.of("data", "notes.txt");

// Whole-file operations
Files.writeString(path, "first line\nsecond line\n");
String all = Files.readString(path);
List<String> lines = Files.readAllLines(path);

// Append instead of overwrite
Files.writeString(path, "third line\n", StandardOpenOption.APPEND);

// Stream a large file lazily — never loads it all into memory
try (Stream<String> stream = Files.lines(path)) {
    long errors = stream.filter(l -> l.contains("ERROR")).count();
    System.out.println(errors);
}

// Buffered writer for many small writes
try (BufferedWriter w = Files.newBufferedWriter(Path.of("out.csv"))) {
    w.write("id,name");
    w.newLine();
    w.write("1,Ada");
    w.newLine();
}

// Paths and directories
Files.createDirectories(Path.of("data/archive"));
System.out.println(Files.exists(path) + " " + Files.size(path));
try (Stream<Path> walk = Files.walk(Path.of("data"))) {
    walk.filter(Files::isRegularFile).forEach(System.out::println);
}
Java
// Records: immutable data carriers in one line.
// You get constructor, accessors, equals, hashCode and toString for free.
record Point(int x, int y) {
    // Compact constructor for validation
    Point {
        if (x < 0 || y < 0) throw new IllegalArgumentException("negative coordinate");
    }
    // Extra methods are allowed
    double distanceTo(Point other) {
        return Math.hypot(x - other.x, y - other.y);
    }
    static Point origin() { return new Point(0, 0); }
}

var a = new Point(3, 4);
System.out.println(a);                       // Point[x=3, y=4]
System.out.println(a.distanceTo(Point.origin()));   // 5.0
System.out.println(a.equals(new Point(3, 4)));      // true — value equality

// Sealed types: a closed set of subtypes the compiler knows about
sealed interface Event permits Click, KeyPress, Scroll {}
record Click(int x, int y) implements Event {}
record KeyPress(char key) implements Event {}
record Scroll(int amount) implements Event {}
Java
// Pattern matching over a sealed hierarchy — exhaustive, no default needed.
static String describe(Event e) {
    return switch (e) {
        case Click(int x, int y) when x == y -> "diagonal click at " + x;
        case Click(int x, int y)             -> "click at " + x + "," + y;
        case KeyPress(char k)                -> "key " + k;
        case Scroll s                        -> "scrolled " + s.amount();
    };
}

// instanceof pattern removes the cast
static int length(Object o) {
    if (o instanceof String s) return s.length();     // s is in scope, already cast
    if (o instanceof Collection<?> c) return c.size();
    return -1;
}

// Text blocks for multi-line strings
String query = """
    SELECT id, name
    FROM users
    WHERE active = true
    ORDER BY name
    """;

// var for local type inference (the type is still static, just inferred)
var counts = new HashMap<String, Integer>();
for (var entry : counts.entrySet()) { /* ... */ }
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.