For exception handling, both Scala and Java have similar
try/catch/finally constructs. In addition, exception handling in Scala can be done using the various member classes of the
scala.util.control.Exception object, such as
Catch,
Try,
Catcher, and
Finally, in stead of the
try/catch/finally construct. In this tutorial, we are going to see examples of both approaches.
try/catch/finally Construct
There is a small difference between Scala and Java's
try/catch/finally constructs. In Java, one may have multiple
catch clauses for a single
try clause, each for a exception class (the
Exception class itself or its subclass). In Scala, only one
catch clause is needed, and multiple
cases are used to handle multiple exception classes. Listing 1 below illustrate such exception handling in Scala.
Listing 1 - Example Using the try/catch/finally Construct
package exception.handling.examples
import scala.io.Source
import java.io.IOException
import java.io.FileNotFoundException
object TryClauseExample extends App {
val fileName = "c:\\temp\\test.txt"
val content =
try {
Source.fromFile(fileName).getLines.mkString("\n")
} catch {
case e: FileNotFoundException => throw new SystemError(e)
case e: IOException => throw new SystemError(e)
}
println(content)
}
In Listing 1, the simple program reads a file from the file system, store the file content in a variable named
content, and print
content's value to the console. If a
FileNotFoundException, or a more general
IOException occurs, it wraps the exception into a custom excepton named
SystemError (see Listing 2) and throws the
SystemError exception. Since
FileNotFoundException is not treated in any way different from the more general
IOException, one case will be sufficient. We used two cases in the example just to show how to handle different
Exception subclasses.
Listing 2 - SystemError
package exception.handling.examples
class SystemError(message: String, nestedException: Throwable) extends Exception(message, nestedException) {
def this() = this("", null)
def this(message: String) = this(message, null)
def this(nestedException : Throwable) = this("", nestedException)
}
scala.util.control.Exception object and its Member Classes
It is not necessary to use the
try/catch/finally construct to handle exceptions. The same thing can be done using various methods and nested classes of the singleton object
scala.util.control.Exception. In this tutorial, we are just going to refer to this singleton object as the
Exception singleton. The program in Listing 3 is equivalent to the program in Listing 1, but does not use the
try/catch/finally construct.
Listing 3 - First Example Using the Catch Class
package exception.handling.examples
import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source
object CatchClassExample1 extends App {
val fileName = "c:\\temp\\test.txt"
val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
val content = fileCatch{Source.fromFile(fileName).getLines.mkString("\n")}
println(content)
}
The most important thing about the
Exception singleton is its (nested) member class
Catch. Every object of this class captures the exception handling logic usually captured by the
catch (and optionally the
finally) part of a
try/catch/finally statement. In general, such exception logic are about:
- What exception, e.g. FileNotFoundException and IOException, to catch
- What to do when catching each of those exceptions, e.g. wrapping the exception in another exception and throwing the later,
- Optionally, what to do always no mater whether an exception occurred or not, i.e. the body of a finally clause in a try/catch/finally statement
The
Exception singleton has a method whose header is
catching[T](exceptions: Class[_]*): Catch[T]
This method returns a
Catch object. The method parameter
exceptions are the exception classes to be caught by the
Catch object. In our example (Line 11 of Listing 3), calling the
catching method and passing to it two exception classes (i.e.
FileNotFoundException and
IOException) returns a
Catch object that will catch
FileNotFoundException and
IOException.
The
Catch class has a method
withApply, whose header is
withApply[U](f: (Throwable) ⇒ U): Catch[U]
Calling this method on a
Catch object will return another
Catch object that will execute the anonymous function passed as argument to the
withApply method, when a target exception is caught. In our example (Line 11 of Listing 3), variable
fileCatch references a
Catch object that will catch FileNotFoundException and IOException, and will throw a custom exception - SystemError - that wraps the caught exception.
The Catch class has a method apply, whose header is
apply[U >: T](body: ⇒ U): U
Calling the apply method, passing a code block as argument to the method, will execute the code block, like the case of a try clause in a try/catch/finally statement. If that code block throw any exception targeted by the Catch object, the Catch object will catch the exception and execute another code block - the one passed to it via calling its withApply method.
This explains why the two programs in Listing 3 and Listing 1 are equivalent.
Listing 4 - Second Example Using the Catch Class
package exception.handling.examples
import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source
object CatchClassExample1 extends App {
val fileName = "c:\\temp\\test.txt"
val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
val content = fileCatch.opt{Source.fromFile(fileName).getLines.mkString("\n")}
println(content.getOrElse("The file cannot be found or read"))
}
Listing 4 is slightly different from Listing 5. In Line 13, instead of the apply method, the opt method, of the Catch object is called. The opt method, like the apply method, takes a code block as parameter, and executes the code block. If calling the apply method with a certain code block as argument returns normally, i.e. not throwing exception, calling the opt method with the same code block as argument will return a Some object wrapping that normal result; if calling the apply method with a certain code block as argument throws an exception, calling the opt method with the same code block as argument will return None. The program in Listing 4 will print the file content to the console if the file can be found and read. If the file cannot be found or read, it will print string "The file cannot be found or read" to the console.
Listing 5 - Third Example Using the Catch Class
package exception.handling.examples
import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source
object CatchClassExample1 extends App {
val fileName = "c:\\temp\\test.txt"
val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
val content = fileCatch.either{Source.fromFile(fileName).getLines.mkString("\n")}
println(content.right.getOrElse("The file cannot be found or read"))
}
Listing 5 is also slightly different from Listing 3. In Line 13, instead of the apply method, the either method, of the Catch object is called. The either method, like the apply method, takes a code block as parameter, and executes the code block. If calling the apply method with a certain code block as argument returns normally, i.e. not throwing exception, calling the either method with the same code block as argument will return a Right object wrapping that normal result; if calling the apply method with a certain code block as argument throws an exception, calling the either method with the same code block as argument will return a Left object wrapping the exception. The program in Listing 5 will print the file content to the console if the file can be found and read. If the file cannot be found or read, it will print string "The file cannot be found or read" to the console.
Conclusion
The exception handling with the Catch class has two advantages comparing with the traditional try/catch/finally construct approach:
- reusing exception handling logic. That is "Don't Repeat Yourself". A Catch object captures a certain exception handling logic. One can call the apply (or opt, or either) on the same Catch object many times, each time with different argument - code block equivalent to the body of a try clause. In the case of the try/catch/finally construct approach, when there are multiple try/catch/finally statements only differ in the try clause, the catch and finally clause still has to be duplicated for each try clause.
- Supporting Monad, with the opt and either methods of the Catch class
The exception handling with the Catch class also has two disadvantages
comparing with the traditional try/catch/finally construct approach:
- Unfamiliar to a large body of programmers, while almost every programmer with basic knowledge of Object-Oriented programming is familiar with the try/catch/finally construct.
- Scattering the exception handling logic about what exceptions to catch, what to do when catch them, and the code that throws those exception into different location in source code.When the gaps between those locations are large, likely the logic will be more difficult to understand than other wise.
The first disadvantage can be overcome with a little training. In general the second disadvantage cannot be easily overcome. Programmers need to be cautious and weight the costs over benefits while choosing one approach over the other.