”;
Exception handling is required in any programming language to handle the runtime errors so that the normal flow of the application can be maintained. Exception usually disrupts the normal flow of the application, which is the reason why we need to use exception handling in our application.
Exception is broadly classified into the following categories −
-
Checked Exception − The classes that extend Throwable class except RuntimeException and Error are known as checked exceptions. E.g. IOException, SQLException, etc. Checked exceptions are checked at compile-time.
Let’s consider the following program which does an operation on a file called Example.txt. However, there can be always a case wherein the file Example.txt does not exist.
(ns clojure.examples.example (:gen-class)) ;; This program displays Hello World (defn Example [] (def string1 (slurp "Example.txt")) (println string1)) (Example)
If the file Example.txt does not exist, then the following exception will be generated by the program.
Caused by: java.io.FileNotFoundException: Example.txt (No such file or directory) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.<init>(FileInputStream.java:138) at clojure.java.io$fn__9185.invoke(io.clj:229) at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69) at clojure.java.io$fn__9197.invoke(io.clj:258) at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69)
From the above exception, we can clearly see that the program raised a FileNotFoundException.
-
Unchecked Exception − The classes that extend RuntimeException are known as unchecked exceptions. For example, ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException, etc. Unchecked exceptions are not checked at compile-time rather they are checked at runtime.
One classical case is the ArrayIndexOutOfBoundsException which happens when you try to access an index of an array which is greater than the length of the array. Following is a typical example of this sort of mistake.
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (aget (int-array [1 2 3]) 5) (catch Exception e (println (str "caught exception: " (.toString e)))) (finally (println "This is our final block"))) (println "Let''s move on")) (Example)
When the above code is executed, the following exception will be raised.
caught exception: java.lang.ArrayIndexOutOfBoundsException: 5 This is our final block Let''s move on
Error
Error is irrecoverable e.g. OutOfMemoryError, VirtualMachineError, AssertionError, etc. These are errors which the program can never recover from and will cause the program to crash. We now need some mechanism to catch these exceptions so that the program can continue to run if these exceptions exist.
The following diagram shows how the hierarchy of exceptions in Clojure is organized. It’s all based on the hierarchy defined in Java.
Catching Exceptions
Just like other programming languages, Clojure provides the normal ‘try-catch‘ block to catch exceptions as and when they occur.
Following is the general syntax of the try-catch block.
(try (//Protected code) catch Exception e1) (//Catch block)
All of your code which could raise an exception is placed in the Protected code block.
In the catch block, you can write custom code to handle your exception so that the application can recover from the exception.
Let’s look at our earlier example which generated a file-not-found exception and see how we can use the try catch block to catch the exception raised by the program.
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch Exception e (println (str "caught exception: " (.getMessage e)))))) (Example)
The above program produces the following output.
caught exception: Example.txt (No such file or directory)
From the above code, we wrap out faulty code in the try block. In the catch block, we are just catching our exception and outputting a message that an exception has occurred. So, we now have a meaningful way of capturing the exception, which is generated by the program.
Multiple Catch Blocks
One can have multiple catch blocks to handle multiple types of exceptions. For each catch block, depending on the type of exception raised you would write code to handle it accordingly.
Let’s modify our earlier code to include two catch blocks, one which is specific for our file not found exception and the other is for a general exception block.
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch java.io.FileNotFoundException e (println (str "caught file exception: " (.getMessage e)))) (catch Exception e (println (str "caught exception: " (.getMessage e))))) (println "Let''s move on")) (Example)
The above program produces the following output.
caught file exception: Example.txt (No such file or directory) Let''s move on
From the above output, we can clearly see that our exception was caught by the ‘FileNotFoundException’ catch block and not the general one.
Finally Block
The finally block follows a try block or a catch block. A finally block of code always executes, irrespective of occurrence of an Exception.
Using a finally block allows you to run any cleanup-type statements that you want to execute, no matter what happens in the protected code. Following is the syntax for this block.
(try (//Protected code) catch Exception e1) (//Catch block) (finally //Cleanup code)
Let’s modify the above code and add the finally block of code. Following is the code snippet.
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch java.io.FileNotFoundException e (println (str "caught file exception: " (.getMessage e)))) (catch Exception e (println (str "caught exception: " (.getMessage e)))) (finally (println "This is our final block"))) (println "Let''s move on")) (Example)
The above program produces the following output.
caught file exception: Example.txt (No such file or directory) This is our final block Let''s move on
From the above program, you can see that the final block is also implemented after the catch block catches the required exception.
Since Clojure derives its exception handling from Java, similar to Java, the following methods are available in Clojure for managing the exceptions.
-
public String getMessage() − Returns a detailed message about the exception that has occurred. This message is initialized in the Throwable constructor.
-
public Throwable getCause() − Returns the cause of the exception as represented by a Throwable object.
-
public String toString() − Returns the name of the class concatenated with the result of getMessage().
-
public void printStackTrace() − Prints the result of toString() along with the stack trace to System.err, the error output stream.
-
public StackTraceElement [] getStackTrace() − Returns an array containing each element on the stack trace. The element at index 0 represents the top of the call stack, and the last element in the array represents the method at the bottom of the call stack.
-
public Throwable fillInStackTrace() − Fills the stack trace of this Throwable object with the current stack trace, adding to any previous information in the stack trace.
Following is the example code that uses some of the methods listed above.
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch java.io.FileNotFoundException e (println (str "caught file exception: " (.toString e)))) (catch Exception e (println (str "caught exception: " (.toString e)))) (finally (println "This is our final block"))) (println "Let''s move on")) (Example)
The above program produces the following output.
caught file exception: java.io.FileNotFoundException: Example.txt (No such file or directory) This is our final block Let''s move on
”;