Import Pipeline: Overriding Throws Clauses and a Finally That Returns
An import pipeline declares abstract class Importer { abstract String load(String path) throws IOException; }. Three subclasses follow. CsvImporter overrides load with throws FileNotFoundException. XmlImporter overrides it with throws Exception. JsonImporter overrides it with no throws clause, but its body calls Files.readAllBytes(Paths.get(path)), which throws IOException. A test harness is written as: try { importer.load("data.csv"); return "loaded"; } catch (IOException e) { return "io"; } finally { return "done"; }. The team expects "io" when the file is missing, but the harness returns "done" every time - even when no exception occurs. Two more catch blocks are proposed: catch (IOException | FileNotFoundException e) and, in another method, catch (Exception e) { ... } catch (IOException e) { ... }.
1. For each of the three subclasses, does the `load` method compile? State the rule for throws clauses in overrides.
Read the worked answer
CsvImporter: compiles. An override may declare the same checked exception, a subclass of it, or none at all.FileNotFoundExceptionis a subclass ofIOException, so callers that only handleIOExceptionare still safe.XmlImporter: does not compile. Declaringthrows Exceptionis broader than the parent'sIOException; a caller holding anImporterreference expects to handle onlyIOException, so the override would break that contract. RemovingExceptionor replacing it withIOException/FileNotFoundExceptionfixes it.JsonImporter: does not compile as written.Files.readAllBytesthrows the checkedIOException. Every checked exception a method body can throw must be caught inside the method or declared in itsthrowsclause. Saying nothing while calling a method that throwsIOExceptionis an error. Either addthrows IOException(allowed - identical to the parent) or wrap the call in try/catch and handle/rethrow.
Related rules: unchecked exceptions (RuntimeException and its subclasses) may always be thrown without declaration and may be declared freely; a private method is not inherited so its signature constraints do not apply; constructors are not bound by the parent's throws clause, only super(...)/this(...) calls inside them are.
2. Why does the harness always return "done", and what exactly is lost by the `return` in the `finally` block?
Read the worked answer
Because a return inside finally overrides every other exit from the try statement.
Execution order: the try block runs importer.load(...). Whether it completes normally, returns, or throws, the finally block always runs before control leaves the statement. When finally executes return "done", that return wins:
- If
loadsucceeded, the pendingreturn "loaded"is discarded and "done" is returned. - If
loadthrewIOException, the catch block computesreturn "io", butfinallystill runs first and itsreturndiscards that value and returns "done" instead. - If
loadthrew an unchecked exception (or any exception the catch does not match), thefinallyreturn swallows the exception entirely - the caller sees a normal return of "done" and never learns about the failure.
So the method is effectively return "done"; plus side effects. finally should be used for cleanup (closing resources, releasing locks), never for return, break or continue in code that carries a result or an exception. The modern fix is try-with-resources, which closes resources without suppressing results or exceptions.
3. Do the two proposed multi-catch/ordering snippets compile? Explain both, and note any restrictions that apply to a multi-catch parameter.
Read the worked answer
Neither compiles.
catch (IOException | FileNotFoundException e) is a compile error. Alternatives in a multi-catch must be disjoint; they may not be related by subclassing, because the parameter would then have two possible types and the compiler cannot decide the type of e. FileNotFoundException is a subclass of IOException, so the alternatives are related. Writing catch (FileNotFoundException e) first and catch (IOException e) after is the correct way to give the subclass special handling; alternatively catch only IOException.
catch (Exception e) { ... } catch (IOException e) { ... } is also a compile error - this time the later block is unreachable. IOException is a subtype of Exception, so every IOException is already handled by the first block. Unreachable catch blocks are compile-time errors, not warnings. Ordered from most specific to most general (FileNotFoundException, then IOException, then Exception) it is legal.
Restrictions on a multi-catch parameter: there is exactly one parameter, its type is the least upper bound of the alternatives, it is implicitly final (so e = new IOException(); inside the block will not compile), and its declared type is effectively the common supertype, so you can only call members available on that type. Also remember: catch blocks may never catch a checked exception that the try body cannot throw, and finally is optional but at least one of catch/finally must follow the try.