In this module, you will see what happens when programs encounter unexpected situations.
No matter how carefully you design and test your program, errors are bound to happen one or the other. When they do, chances are your program crashes to a halt. Many techniques have been invented to deal with such errors. Many languages such as C and Assembly, errors must be manually checked and handled by using error codes. This technique is very verbose and inelegant. Zen provides a sophisticated error handling mechanism. Zen implements an object-oriented approach to handling such situations known as exceptions.
In Zen, an exception is an object which represents an unexpected situation in your program at runtime. In other words, an exception is an error that occurs when your program is running. Exceptions allow your programs to recognize errors and respond to them gracefully. In fact, sometimes your program can resolve the situation and resume execution normally.
In Zen, errors of all types are generalized into a special type of object known as an exception. An exception is an object which represents an unexpected event. It occurs when your program is running. It disturbs the flow of your program. Exceptions are used to indicate many types of error conditions which may occur for many reasons. For example, the programmers did not anticipate possible problems, or the application is untested, or the program encounters situations such as corrupt data, corrupt files, network problems, hardware device failure, and so on.
An exception is represented by an object. It holds the information about the event such as the condition, the stacktrace, the location, and a message. The exception object is usually refered to as exception. You can throw and catch an exception. After an exception object is created, it is thrown. The virtual machine attempts to find an exception handler for the exception by backtracking the list of functions that have been called, known as the call stack. If a handler is found, the exception is caught. Otherwise, the virtual machine invokes the default exception handler and terminates the current thread.
Here are some common exceptions.
OutOfMemoryError
StackOverflowError
InvalidPathException
IOException
NullPointerException
ArrayIndexOutOfBoundsException
When an unexpected situation occurs, an exception object is created and thrown.
The function which invoked the current function can handle the exception or
let it pass through. At some point, the exception is caught and handled.
Exceptions can be thrown either by your code, or the virtual machine, or
any libraries your code uses. Exceptions thrown by the virtual machine indicate
errors that violate the rules of Zen. For example, if you try to access an
instance member through a null
reference, the virtual machine throws a
NullPointerException
.
Exception handling in Zen uses the following keywords.
try
catch
throw
finally
In short, when an exception occurs, a new exception object is created.
This object is thrown using a throw statement. Your code can anticipate exceptions
to be thrown from a block of code using the try
clause. If an exception is
thrown within the try
block your code is monitoring, your code can handle this
exception using the catch
clause. Any code that must be executed regardless of
an exception being thrown or not, must be written in the finally
clause.
Here is the general form of a try statement.
try
// Statements that may trigger an exception
statement1
statement2
...
statementN
catch Exception1 exception
// Handle the exception `Exception1`
statement1
statement2
...
statementN
catch Exception2 exception
// Handle the exception `Exception2`
statement1
statement2
...
statementN
finally
// Statements that must be executed regardless of an exception being thrown
statement1
statement2
...
statementN
Here, Exception1
and Exception2
indicate the type of exception to catch.
You will learn more about the mechanics involved throughout this chapter.
Advantages of Exceptions
The advantages of exceptions are as follows.
You can separate error handling from your algorithm.
With exceptions you can separate the details of what happens when an unexpected situation occurs. You can separate such logic from the main logic of your program. This allows you to keep your source code clean and organized.
Your functions do not have to forward errors.
Exceptions have the ability to propagate up the stack of functions currently invoked. For example, assume that a function is the fourth function in a series of nested function calls. If the first function is the only function interested in handling the error, and the error occurs in the fourth function, you don’t have to manually forward the error. Throwing an exception does the job for you. This allows you to generalize error handling in one common place.
You can group and differentiate error conditions.
Because exceptions are objects, you can group or categorize them using their
class hierarchy. An example of a group of related exception classes are the exception
classes which inherit the zen.io.IOException
class. The IOException class
represents an input/output error. Its descendants represent more specific errors.
For example, InvalidPathException
indicates that a path could not be found
on the disk.
All these advantages make exceptions reliable, stable, and secure.
Understanding Exception Types
An exception object contains the information about the error condition. It includes information such as stacktrace and message. Perhaps, the most important information is the cause of the error. It is indicated by the name of the exception class used to create the exception object. Usually, you will use exception objects only to figure out the kind of error that occurred.
Inheritance is currently under development.
In order to understand exceptions fully, you need to understand the class
hierarchy of exceptions first. All exception classes are subclasses of the
Throwable
class. In other words, the Throwable
is the superclass of all
exceptions in the exception class hierarchy. The Throwable
class, like any
other class in Zen, inherits the zen.core.Object
class. You can throw and
catch only the instances of the Throwable
class.
The exception class hierarchy is further divided into two main subclasses.
Exception
Error
The Exception
class represents conditions that your program should catch and
handle. In order to create your own custom exception classes, you need to inherit
this class. InvalidPathException
is an example of such an exception. Further,
the class RuntimeException
is a special subclass of the Exception
class.
Exceptions which are intended to be unchecked should inherit this class.
NullPointerException
is an example of such an exception. You will learn more
about checked and unchecked exceptions in the next lecture.
The second subclass of the Throwable
class is the Error
class. It defines
exceptions that your program should generally not catch. These exceptions are
usually thrown by the virtual machine to indicate errors related to the
environment. OutOfMemoryError
is an example of such an error which indicates
memory exhaustion.
The following code segement shows the public methods found inside the Throwable
class. Please refere the Zen documentation for more reference.
class Throwable
function getMessage()
function getCause()
function toString()
function printStackTrace()
function printStackTrace(stream)
function getStackTrace()
function suppress(exception)
function getSuppressedExceptions()
Here is a list of some typical exceptions.
-
IllegalArgumentException
This exception is thrown to indicate that you passed an incorrect argument to a method. -
ArithmeticException
This exception is thrown to indicate that you tried to perform an illegal arithmetic operation, such as dividing an integer by zero. -
IOException
This exception is thrown to indicate an error that the program encountered when you tried to perform an I/O operation. -
ClassNotFoundException
This exception is thrown to indicate that class coud not be found.
Zen includes many exceptions. You can learn more about them from the Zen Standard API documentation.
Catching Exceptions
Before you learn to catch exceptions in your program, you need to see what happens when you do not catch them. Here is an example which generates an exception.
// GenerateException.zen
function main(...arguments)
var a = 100
var b = 0
var c = a / b
printf('%d / %d = %d\n', a, b, c)
When the virtual machine tries to evalute a / b
, it throws an exception
because you tried to divide an integer by zero.
The output of this example is shown here.
The output contains the type of the exception, an error message and the stacktrace which points to the location from where the exception occurred. The stack trace includes the class names, the function names, the file names, and the line numbers through which the exception propogated.
When an exception is thrown, Zen stops whatever your program is doing and tries to find an exception handler. In this example, this causes the execution of the main function to stop. Since we have not provided an exception handler, the exception propogates outside your function and is caught by the default exception handler.
Every thread in Zen has a default exception handler which is triggered when an exception is never caught. In other words, any exception your program fails to catch is processed by the default exception handler. The default exception handler prints the exception, the stacktrace from the point where the exception was thrown, and terminates the current thread. A thread is a component which allows multiple parts of your program to run simultaneously. In this example, there is only one thread, hence the program is terminated.
Here is another example which demonstrates how the stacktrace can be useful to locate the cause of the error.
// GenerateException2.zen
function divide(a, b)
return a / b
function main(...arguments)
var a = 100
var b = 0
var c = divide(a, b)
printf('%d / %d = %d\n', a, b, c)
As you can see, the stacktrace includes the class name, the function name, the file name, and the line numbers through which the exception propogated. The exception can occur in any source file, any class, and any function. This is why each element in the stacktrace contains these information. In other words, the stack trace will always show the function invocations which led up to the error.
Using the Try and Catch Clauses
You cannot always depend on the default exception handler to handle the exceptions. In some situations, you will want to handle the exception yourself. With a custom exception handler, you can resolve the error and prevent the program from terminating. Imagine, you are writing a server side program. How troublesome would it be if your program terminated whenever an exception occurred?
You need to monitor a block of code for exceptions using the try
clause.
You can then provide a custom exception handler using a catch clause.
Whenever you write a try
clause, you are essentially writing the try statement.
It is a compound statement.
Here is the general form of the try clause.
try
statement1
statement2
...
statementN
A try clause should always be followed by a catch clause or a finally clause. When both the clauses appear, the finally clause should be the last clause.
Here is the general form of the catch clause.
catch ExceptionType identifier
statement1
statement2
...
statementN
The ExceptionType
indicates the type of exception the catch clause handles.
When an exception is caught, the variable identified by identifier
you
specify holds a reference to the exception object. Basically, you declare a
parameter in the parenthesis of the catch clause. The parameter references the
exception object when the exception is caught.
Here is an example which demonstrates how to catch an exception.
// GenerateException3.zen
function divide(a, b)
return a / b
function main(...arguments)
var a = 100
var b = 0
try
var c = divide(a, b)
printf('%d / %d = %d\n', a, b, c)
catch ArithmeticException exception
print('Error: You cannot divide an integer by zero.')
This example generates the following output.
Error: You cannot divide an integer by zero.
Notice that the print statement inside the try clause is never executed. When an exception is thrown, the program stops whatever it is doing. Zen then tries to find the nearest catch clause in the stack trace. When a catch clause which anticipates the exception which occurred is found in the stack trace, the program control jumps out of the try clause and is transferred to the catch clause. A reference to the exception object is stored in the parameter of the catch clause. Once the catch clause is done executing, the statement following the catch clause is executed. The control never returns back to the location where the exception occurred because exceptions are not like functions to return back.
A catch clause can catch an exception only if one of the statements within the try clause associated with it throws an exception.
Multiple Catch Clauses
In some cases, more than one type of exception can be thrown by the statements in your try clause. To handle such situations, you can specify more than one catch clause, each catching a different type of exception. When an exception is thrown, each catch clause is checked in order to see if it handles the exception. The first catch clause whose type matches the thrown exception object is executed. When one catch statement is finished executing, the other catch clauses are skipped. The program control transfers to the statement following the try statement.
Here is an example of a try statement which handles multiple types of exceptions.
// MultipleCatchClauses.zen
function divide(a, b)
return a / b
function main(...arguments)
try
var a = 10
var b = 20
var c = divide(a, b)
printf('%d / %d = %d\n', a, b, c)
var object = null
var string = object.toString()
print(string)
catch ArithmeticException exception
print('Error: You cannot divide an integer by zero.')
catch NullPointerException exception
print('Error: You cannot dereference null.')
Here is the output produced by this example.
0
Error: You cannot dereference null.
The statements within the try clause in this program can generate two exceptions.
You cannot divide an integer by zero. In this example, invoking the divide()
function may produce an ArithmeticException
. Since the arguments are positive integers,
this exception does not occur. We nevertheless provide a catch clause to handle
it. The program successfully divides a
and b
and prints the result.
You cannot dereference a null reference. Doing so will cause Zen to throw a
NullPointerException
. Therefore, we provide a catch clause to handle it.
This program was intentionally designed to produce a NullPointerException
exception. When the exception is thrown, the catch clause which handles the
NullPointerException
exception is triggered. Thus, the second print
statement in the try clause is not executed.
Understanding Multiple Catch Clauses and Inheritance
Inheritance is currently under development. You may skip this section.
When you use multiple catch clauses, you need arrange them properly. Imagine
that you have two catch clauses. The first catch clause handles exceptions of
type A
and the second catch clause handles exceptions of type B
. The order
in which your catch clauses appear does not matter as long as A and B are not
related. In other words, the order does not matter if A
is neither the
superclass or subclass of B
.
However, if A
and B
are related, that is, if A
is either the superclass
or subclass of B
then the order matters. You always need to write the catch
clause which handles the subclass first because Zen always selects the first
suitable clause without considering other clauses. Zen searches through the
catch clauses in the order in which they appear in your source code. If you
first write a catch clause which handles your superclass, the exception object
even if it is an instance of the subclass is implicitly cast by Zen to match
the superclass. This results in the selection of the catch clause which handles
the superclass, not the subclass.
Consider the following example.
// MultipleCatchClauses2.zen
function divide(a, b)
return a / b
function main(...arguments)
try
var a = 10
var b = 20
var c = divide(a, b)
printf('%d / %d = %d\n', a, b, c)
catch Exception exception
print('Error: This is a generic message.')
catch ArithmeticException exception
print('Error: You cannot divide an integer by zero.')
If you try to compile this example, the compiler will generate an error. The error basically means that your second catch clause will never be executed because the first catch clause handles it. You need to change the order of the catch clauses to fix this problem.
Handling all Exceptions in One Place
You can create a general catch clause such that all the exceptions are handled
by a single catch clause. You have learnt how the virtual machine finds exception
handlers. Using this concept, we can create a general catch clause.
You know that the Throwable
class is the superclass of all exceptions. Which
means, if you write a catch clause which handles Throwable
, then all types
of exceptions are handled by it.
Here is the general form of such a catch clause.
try
statement1
statement2
...
statementN
catch Throwable exception
...
Since, exceptions which inherit Error
should not be caught, it is better to
write a catch clause that handles the Exception
class. This way, you can
catch only the exceptions that are meant to be caught.
Here is the general form of such a catch clause.
try
statement1
statement2
...
statementN
catch Exception exception
...
Throwing Exceptions
So far, you have only learnt to catch exceptions. In particular, the exceptions that were thrown by Zen. However, you can throw an exception manually, using the throw statement.
The general form of throw statement is shown here.
throw expression
The throw statement is a simple statement. Therefore, it is terminated by a
semicolon. Here, the expression
must evaluates to an object whose type inherits
Throwable
. It can be an instance of the Throwable
class itself.
Unlike C++, you cannot throw primitive values such as integer or floating-point
decimals. In fact, you cannot throw an instance of a class which does not inherit
the Throwable
class. Which means, you cannot throw objects such as strings
and arrays.
The flow of execution stops immediately after you throw an exception. Any statements following the throw statement are skipped. The nearest enclosing try block is checked to see if it has a catch clause that handles the exception. If it finds a suitable catch clause, then the control is transferred to that clause. Otherwise, the next enclosing try statement is checked, and this process repeats until no try statements are found. In such a case, the default exception handler is triggered. It will print the stack trace and the error message. After which, the thread in which the exception occurred is terminated. So far you have learnt to write single threaded programs. Therefore, if your main thread terminates, your program itself will terminate.
Here is an example program which throws an exception.
// ThrowExample.zen
function printGreetings(name)
if name == 'Chikka Chikka Slim Shady'
throw new IllegalArgumentException('That hardly sounds like a name.')
printf('Hi, %s!\n', name)
function main(...arguments)
try
printGreetings('Chikka Chikka Slim Shady')
catch IllegalArgumentException exception
print('Error: ' + exception.message)
This program generates the following output.
Error: That hardly sounds like a name.
The printGreetings()
function validates the name before printing it. It considers
'Chikka Chikka Slim Shady'
an invalid name. Therefore, when you specify this name
it throws an exception. The IllegalArgumentException
class is an in-built Zen
class. It is thrown to indicate an illegal argument passed to a function.
You can pass a message to its constructor.
In the main()
function, we invoke the printGreetings()
function with "Chikka Chikka Slim Shady"
as the name. This causes it to throw an exception. The
main function catches this exception. You can retrieve the message that you
specified to the constructor of the IllegalArgumentException
with the message
property. The catch clause retrieves the message and prints it on the console.
Understanding Checked and Unchecked Exceptions
The concepts of checked and unchecked exceptions were inspired from Java. These concepts are simplified in Zen and are observed only as design principles, meaning the compiler does not enforce these rules. However, the compiler marks each function with the exceptions it may throw. This information can be used outside Zen by other tools.
An unchecked exception is an exception which you can handle if you want.
Any exception class which inherits the RuntimeException
class is an unchecked
exception. The compiler does not force you to handle it. It is assumed that the
application cannot do anything to recover from such exceptions at runtime.
So far, all the exceptions you have used are unchecked exceptions.
A checked exception is an exception which you need to handle. However, the
compiler does not force you to handle it. Any exception class which inherits the
Exception
class is a checked exception. An example of a checked exception is the
IOException
class.
Unchecked exceptions allow you to ignore exceptions that you cannot recover from, and only handle the ones you can. This leads to less clutter. However, many programmers simply ignore unchecked exceptions. Turning all exceptions into unchecked exceptions would lead to complicated error handling.
Using the Finally Clause
When an exception is thrown, the execution of your code jumps abruptly, possibly from one function to another function. This means some important segment of your code may be skipped. This could be a problem in some functions. For example, if a function opens a file in the beginning and closes it before returning, then you will not want the code that closes the file to be skipped by an exception. The finally clause is designed to control such situations.
You can create a finally clause in a try statement. The finally clause is basically a block of code that will always be executed, regardless of an exception being thrown or not. If an exception is thrown, the finally block will execute even if no catch clause matches.
If a function is returns to the caller from inside a try/catch clauses, via an uncaught exception or a return statement, the finally clause is executed just before the function returns. The finally clause is optional. However, a try clause requires at least one catch or a finally clause to follow it.
// FinallyExample.zen
function divide(a, b)
return a / b
function main(...arguments)
try
var a = 10
var b = 0
var c = divide(a, b)
printf('%d / %d = %d\n', a, b, c)
finally
print('This statement was executed.')
As you can see, the print statement inside the finally clause was executed even when an exception was thrown. Without the finally clause the exception would have prevented the print statement from being executed.