Java Arena
Step 6 of 7

Exceptions and interfaces

An interface lists the headers of methods (module 35), and a header can include throws. Then it says, for every class that implements the interface, which checked exceptions that method may throw.

Door codes

The doors of an office building open with a code typed on a keypad. Some doors have one fixed code; others check a file of codes that the office manager keeps up to date. The keypad doesn't care which: it needs something that can answer "is this code valid?". Reading a file can fail, and the interface says so:

import java.io.IOException;

public interface DoorCodes {
    boolean isValid(String code) throws IOException;
}

Here are two classes that implement it, and three doors:

The file staff.txt
4711
2580
Main.java
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        open("Bike shed", new FixedCode("1234"), "2580");
        open("Staff entrance", new CodeFile("staff.txt"), "2580");
        open("Guest entrance", new CodeFile("guests.txt"), "2580");
    }

    public static void open(String door, DoorCodes codes, String typed) {
        try {
            if (codes.isValid(typed)) {
                System.out.println(door + ": the door opens");
            } else {
                System.out.println(door + ": wrong code");
            }
        } catch (IOException e) {
            System.out.println(door + ": out of order, can't read " + e.getMessage());
        }
    }
}
DoorCodes.java
import java.io.IOException;

public interface DoorCodes {
    boolean isValid(String code) throws IOException;
}
FixedCode.java
public class FixedCode implements DoorCodes {
    private String code;

    public FixedCode(String code) {
        this.code = code;
    }

    @Override
    public boolean isValid(String code) {
        return this.code.equals(code);
    }
}
CodeFile.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CodeFile implements DoorCodes {
    private String fileName;

    public CodeFile(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public boolean isValid(String code) throws IOException {
        return Files.readAllLines(Paths.get(this.fileName)).contains(code);
    }
}
Output
Bike shed: wrong code
Staff entrance: the door opens
Guest entrance: out of order, can't read guests.txt
  • CodeFile reads a file, which can throw an IOException, and its isValid passes it on with throws IOException, as the interface allows.
  • FixedCode can't fail, and its isValid has no throws at all. That's allowed: an implementation may throw fewer checked exceptions than the interface says, or none.
  • open gets a DoorCodes. All it knows is the interface's header, so it must be ready for the IOException, even when the object behind it happens to be a FixedCode.

What an implementation may throw

The interface is a promise to the code that uses it: open catches IOException because the interface says that's what can come. If an implementation could throw some other checked exception, open wouldn't be ready for it. So javac refuses an implementation that declares a checked exception the interface doesn't list, or a broader one:

Main.java
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        DoorCodes codes = new CodeFile("staff.txt");
        try {
            System.out.println(codes.isValid("2580"));
        } catch (IOException e) {
            System.out.println("Out of order");
        }
    }
}
DoorCodes.java
import java.io.IOException;

public interface DoorCodes {
    boolean isValid(String code) throws IOException;
}
CodeFile.java
import java.nio.file.Files;
import java.nio.file.Paths;

public class CodeFile implements DoorCodes {
    private String fileName;

    public CodeFile(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public boolean isValid(String code) throws Exception {
        return Files.readAllLines(Paths.get(this.fileName)).contains(code);
    }
}
What javac prints
CodeFile.java:12: error: isValid(String) in CodeFile cannot implement isValid(String) in DoorCodes
    public boolean isValid(String code) throws Exception {
                   ^
  overridden method does not throw Exception
1 error

Exception is broader than IOException: it includes checked exceptions that open doesn't catch. In short:

  • An implementation may declare the interface's checked exceptions, or classes below them (such as NoSuchFileException), or leave them out.
  • It may not add other checked exceptions.
  • Unchecked exceptions are always allowed, since nobody has to declare or catch them. FixedCode's isValid could throw an IllegalArgumentException when the typed code is empty, and it still needs no throws.

The same rule holds for a method that overrides a method of a superclass (module 34).

The variable's type decides

Whether a call needs a try depends on the header javac sees, and that's the header in the variable's type. Through a FixedCode variable, isValid throws nothing checked, so no try is needed:

Main.java
public class Main {
    public static void main(String[] args) {
        FixedCode shed = new FixedCode("1234");
        System.out.println(shed.isValid("1234"));
    }
}
DoorCodes.java
import java.io.IOException;

public interface DoorCodes {
    boolean isValid(String code) throws IOException;
}
FixedCode.java
public class FixedCode implements DoorCodes {
    private String code;

    public FixedCode(String code) {
        this.code = code;
    }

    @Override
    public boolean isValid(String code) {
        return this.code.equals(code);
    }
}
Output
true

Through a DoorCodes variable, the same call on the same object doesn't compile without one:

Main.java
public class Main {
    public static void main(String[] args) {
        DoorCodes shed = new FixedCode("1234");
        System.out.println(shed.isValid("1234"));
    }
}
DoorCodes.java
import java.io.IOException;

public interface DoorCodes {
    boolean isValid(String code) throws IOException;
}
FixedCode.java
public class FixedCode implements DoorCodes {
    private String code;

    public FixedCode(String code) {
        this.code = code;
    }

    @Override
    public boolean isValid(String code) {
        return this.code.equals(code);
    }
}
What javac prints
Main.java:4: error: unreported exception IOException; must be caught or declared to be thrown
        System.out.println(shed.isValid("1234"));
                                       ^
1 error

throws Exception, and AutoCloseable

Interfaces often declare throws Exception, the widest promise: then each implementation may throw whatever it needs to. Java's own AutoCloseable from step 4 is one of them: its method is void close() throws Exception. The Faucet's close() left the throws out, since turning off a faucet can't fail. That's why its try-with-resources didn't need a catch (Exception e): javac knew from the Faucet's own header that closing it throws nothing checked.

Your task

Challenge 1 of 3

A door can also keep a short list of codes in memory. DoorCodes.java is ready. In CodeList.java, write the class CodeList, which implements DoorCodes, with:

  • a constructor with no parameters; a new CodeList has no codes
  • add(String code), which adds a code to the list
  • isValid(String code), with @Override, which returns true if the code is on the list

A list in memory can't fail to be read, so leave out the throws. Main.java calls isValid through a CodeList variable without a try, so it only compiles when CodeList's isValid throws nothing checked. The check runs:

CodeList codes = new CodeList();
codes.add("1357");
codes.add("8642");
System.out.println(codes.isValid("8642"));
System.out.println(codes.isValid("1234"));

and expects:

true
false

Challenge 2

This keypad locks itself after too many wrong codes. Its isValid throws no checked exception, but it may throw an unchecked one. What does the program print?

Challenge 3

Another door's code changes every day: the first line of today.txt is today's code. DailyCode.java reads it with a Scanner in a try-with-resources without a catch, so that its isValid passes the IOException on. The program reads a typed code and prints The door opens or Wrong code, but it doesn't compile. Fix it:

  • DailyCode's isValid declares more than DoorCodes allows. Make it declare what the interface says.
  • Then main has to deal with the exception: when the code can't be read, the program prints Keypad out of order.

For the input 7294 and the file shown below, the program prints:

The door opens