- 2.1 Conditional Expressions
- 2.2 Statement Termination
- 2.3 Block Expressions and Assignments
- 2.4 Input and Output
- 2.5 Loops
- 2.6 Advanced for Loops and for Comprehensions
- 2.7 Functions
- 2.8 Default and Named Arguments L1
- 2.9 Variable Arguments L1
- 2.10 Procedures
- 2.11 Lazy Values L1
- 2.12 Exceptions
- Exercises
2.12 Exceptions
Scala exceptions work the same way as in Java or C++. When you throw an exception, for example
throw new IllegalArgumentException("x should not be negative")
the current computation is aborted, and the runtime system looks for an exception handler that can accept an IllegalArgumentException. Control resumes with the innermost such handler. If no such handler exists, the program terminates.
As in Java, the objects that you throw need to belong to a subclass of java.lang.Throwable. However, unlike Java, Scala has no “checked” exceptions—you never have to declare that a function or method might throw an exception.
A throw expression has the special type Nothing. That is useful in if/else expressions. If one branch has type Nothing, the type of the if/else expression is the type of the other branch. For example, consider
if (x >= 0) { sqrt(x) } else throw new IllegalArgumentException("x should not be negative")
The first branch has type Double, the second has type Nothing. Therefore, the if/else expression also has type Double.
The syntax for catching exceptions is modeled after the pattern matching syntax (see Chapter 14).
try { process(new url("https://horstmann.com/fred-tiny.gif")) } catch { case _: MalformedURLException => println("Bad URL: " + url) case ex: IOException => ex.printStackTrace() }
As in Java or C++, the more general exception types should come after the more specific ones.
Note that you can use _ for the variable name if you don’t need it.
The try/finally statement lets you dispose of a resource whether or not an exception has occurred. For example:
var in = new url("https://horstmann.com/fred.gif").openStream() try { process(in) } finally { in.close() }
The finally clause is executed whether or not the process function throws an exception. The reader is always closed.
This code is a bit subtle, and it raises several issues.
- What if the URL constructor or the openStream method throws an exception? Then the try block is never entered, and neither is the finally clause. That’s just as well—in was never initialized, so it makes no sense to invoke close on it.
- Why isn’t val in = new URL(...).openStream() inside the try block? Then the scope of in would not extend to the finally clause.
- What if in.close() throws an exception? Then that exception is thrown out of the statement, superseding any earlier one. (This is just like in Java, and it isn’t very nice. Ideally, the old exception would stay attached to the new one.)
Note that try/catch and try/finally have complementary goals. The try/catch statement handles exceptions, and the try/finally statement takes some action (usually cleanup) when an exception is not handled. It is possible to combine them into a single try/catch/finally statement:
try { ... } catch { ... } finally { ... }
This is the same as
try { try { ... } catch { ... } } finally { ... }
However, that combination is rarely useful.