Room 14 of 15 · about 25 minutes

Writing a file with PrintStream

Every room so far has read a file that already existed. This room makes one. It is also where the line from room 6 finally gets used for a second reason, because the writing constructor throws the same checked exception the reading one does.

Tasks checked0 of 4 XP earned on this path0

What this room checks you can do

Student can construct a PrintStream over a File, declare or catch the FileNotFoundException the constructor throws, write output with println/printf, and close() the stream to flush buffered bytes.

Notes

Constructing a PrintStream on a File

To write to a file, construct a PrintStream over a File and then use the same println / print / printf methods you have used on System.out.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;

public static void writeReport(final String filename) throws FileNotFoundException {
    final PrintStream out = new PrintStream(new File(filename));
    out.println("Report");
    out.println("------");
    out.printf("Pi: %.4f%n", Math.PI);
    out.close();   // flush + release the OS file handle
}

Four things to know about this constructor:

1. It creates the file if it does not exist. Unlike the read side (where a missing file causes FileNotFoundException), the write side's FileNotFoundException only fires when the path is invalid: typically because a directory in the path does not exist, or the program lacks permission to write there. A missing leaf file is created.

2. It overwrites any existing file with the same name. Opening for write truncates the file to length zero before any data is written. If you wanted to append instead, you would need the (File, String) or (File, Charset) overload with append=true, but the simple constructor truncates.

3. It is declared to throw FileNotFoundException (same as the Scanner(File) constructor on the read side). The handle-or-declare rule (JLS §11.2) applies; every method that constructs a PrintStream(File) must declare or catch.

4. Buffering means close() is mandatory. PrintStream buffers output internally. Bytes you "printed" may still be in the buffer when the program exits, and if the JVM crashes before close() runs, those bytes are lost. Calling close() flushes the buffer to disk and releases the OS file handle. Forgetting to close is the classic "my file is empty even though I printed to it" bug.

When the directory does not exist

new PrintStream(new File("missing-dir/out.txt")) throws FileNotFoundException with the message "missing-dir/out.txt (No such file or directory)". The constructor does not create parent directories. Pre-flight by checking the parent exists, or use f.getParentFile().mkdirs() if creating the tree is acceptable.

In other languages

  • Python: open(filename, "w") truncates; open(filename, "a") appends. Use a with block to auto-close.
  • C: fopen(filename, "w") truncates; fopen(filename, "a") appends. fclose is mandatory.
  • Bash: > file.txt truncates; >> file.txt appends.

FileNotFoundException is checked

When new Scanner(new File(filename)) runs and the file is not on disk, the constructor throws java.io.FileNotFoundException. This is a checked exception, meaning the compiler enforces a binary rule (JLS §11.2): the enclosing method must either catch the exception or declare it on the method header with throws. Forgetting both is not a runtime concern: it is a compile error.

import java.io.File;
import java.io.FileNotFoundException;   // exactly this import; see pitfalls
import java.util.Scanner;

public static int countValues(final String filename) throws FileNotFoundException {
    final Scanner sc = new Scanner(new File(filename));
    // ...
}

Two facts about checked-exception declarations that the compiler enforces and students consistently underestimate:

1. A pre-check does not remove the throws requirement. Even if you wrote if (!new File(filename).exists()) { ... } two lines above the new Scanner(...), the compiler still demands throws FileNotFoundException on the method header. The compiler does not perform flow analysis across statements to discover that the exception is unreachable. It sees a call to a constructor that can throw FileNotFoundException and requires you to acknowledge it.

2. FileNotFoundException lives in java.io, not java.nio.file. The java.nio.file package has its own family of exceptions for the newer NIO.2 API (FileSystemNotFoundException, NoSuchFileException). They are different types with different superclasses. Importing the wrong one will not satisfy the throws requirement on a header that was written against java.io.FileNotFoundException, and every method body that mentions the type will fail to resolve.

In other languages

  • Python: open("missing.txt") raises FileNotFoundError: unchecked, no header declaration required.
  • C: fopen returns NULL instead of raising; the caller must check, no compile-time enforcement.
  • C#: FileNotFoundException exists but is unchecked (the language has no checked-exception concept).

What this room assumes you already have

Tasks

Do each one, then check the box. Checking a box is you saying you did it. You can uncheck a box if you check it by accident.

  1. trace
    Show the answer

    the console.

  2. write
    Show the answer

    add PrintStream out parameter, call out.println instead.

  3. trace
    Show the answer

    it is truncated to zero length before any new write.

  4. write
    Show the answer

    open PrintStream, out.println("Count: " + n), close.

Self check

Type what you think the answer is. Getting it wrong costs nothing and you can try as many times as you want.

Given new PrintStream(new File("nope/out.txt")) on a system where the nope directory does not exist, predict the result.

Practice, untimed

Open this whenever you want, before the tasks or after them. Nothing in this section is recorded and nothing here is timed.

  1. Given the same method and the call f(new PrintStream(new File("log.txt"))), predict the output destination.trace
    Show the answer

    the file log.txt.

  2. Given a method that hard-codes System.out.println in its body, identify what stops the caller from redirecting the output to a file.trace
    Show the answer

    the destination is fixed inside the method body: there is no parameter to change.

  3. Given a program that opens a PrintStream, calls out.println("hello"), and exits without calling close(), predict the file's contents.trace
    Show the answer

    undefined: buffered output may or may not have been flushed. Often the file is empty.

Optional challenge

This one is optional. Do what the room says you can do, without opening any answers, then read the two traps below and check your work against them. Each trap is copied from the notes for this room.

Student can construct a PrintStream over a File, declare or catch the FileNotFoundException the constructor throws, write output with println/printf, and close() the stream to flush buffered bytes.

How this room finishes

This room is done when all four tasks are checked and the self check is answered.