Practice 10
The material for the practice¶
Exceptions - error handling¶
Exceptions represent some kind of exceptional event, and in Java they are used to handle errors.
Recall what happens when you over- or under-index an array! If you can't remember, try it!
If you write something in the code incorrectly, like missing a semicolon or a bracket, you will get a compile-time error when you try to compile the code. However, not all errors are detected at compile time, such as io operations, user interaction, or network communication. These errors are handled by Java exceptions, more precisely by exception objects which are "thrown" when an error occurs, and which are caught and handled at the call site, or re-thrown. In effect, this is "trying to save what can be saved", i.e. if an exception event occurs but is not fatal to the operation of the program, we somehow handle the error so that the program can continue to run without problems (for example, in a calculator application, trying to divide by zero is an exception event, but you can continue to use the calculator after an error message).
When an error occurs in a method during the execution of the program (i.e. an exception event that does not occur during the normal operation of the program), an exception object is created within the method (or received from a method we have called) in memory, passed to the runtime environment, the execution of the method is stopped, and the exception object created is passed to the caller, who can handle it or re-throw it.
If the error is handled at the call site (this should be provided by default), it is handled. If we have more than one error handling code, the first appropriate one will be used, and then the program will continue to run as normal.
So, to summarize so far, we know that exceptions are special objects that are thrown for some kind of error, and we can handle them. But how should we imagine this in Java?
throw¶
If we have a method where certain conditions hold, and we want an error to be thrown (for example, if we get a number 0 in a method and would divide by it), we need to create an exception object there, which we can store in reference if we want (we usually don't); we then need to throw this exception, for which the throw
keyword will be used.
public int division(int a, int b) {
if(b == 0) {
// In this case we want to throw an error
// We want to throw, but what?
}
return a/b;
}
Another example is the animal example from earlier, where everyone's homework was to implement whether there could be only land animals or only aquatic animals in a herd, since a mixed herd is not really workable. The modified Herd class:
import animals.*;
public class Herd {
private Animal[] members;
private int maximum = 0;
private int current;
private boolean terrestrialAnimals;
public Herd(int num) {
this.members = new Animal[num];
this.maximum = num;
this.current = 0;
}
public boolean acceptIntoHerd(Animal what) {
if (current == 0) {
if (what instanceof TerrestrialAnimal) {
terrestrialAnimals = true;
}
}
if (current < maximum) {
if ( (terrestrialAnimals && what instanceof AquaticAnimal) ||
(!terrestrialAnimals && what instanceof TerrestrialAnimal) ) {
// This is where we should throw an error
}
members[current] = what;
current++;
return true;
}
return false;
}
public String toString() {
String returnValue = "Animals: ";
for (int i = 0; i < current; ++i) {
returnValue += this.nenbers[i].getName();
returnValue += ", ";
}
return returnValue;
}
}
Exception¶
All we need now is an exception object. This can be an instance of your own exception class, or one of the built-in Java exceptions. The parent class of exceptions in Java is Exception
. It is a generic exception class, all exception classes derive from it. Some additional exception classes are:
- ArithmeticException (e.g.: division by zero)
- ArrayIndexOutOfBoundsException (array indexing)
- IllegalArgumentException
- IOException (related to IO operations)
- SQLException
- NullPointerException
- ClassNotFoundException
RuntimeException¶
Parent of runtime exceptions. Catching these is optional, as they are not thrown during normal program execution; one of them is the NullPointerException
. You can handle these if you want, but it may not be a good idea, because when these exceptions are thrown you would usually prefer the program to stop running and terminate completely.
Throwable¶
The parent of exceptions (the Exception
class and its children) and Error
classes. The Error
type indicates a more serious problem, which is not usually dealt with (such as a virtual machine failure or running out of memory). The class hierarchy is shown in the figure below:
If you want to make your own exception class, all you have to do is inherit it from the parent class, Exception
. A simple example of this is the IncompatibleAnimals
class, which we will throw for incompatible animals.
package animals.exception;
public class IncompatibleAllats extends Exception {
public IncompatibleAnimals() {
super();
}
public IncompatibleAnimals(String message) {
super(message);
}
}
Where we want to drop this, we just need to instantiate it and then pass the object we created to throw
. The completed throw
method:
public boolean acceptIntoHerd(Animal what) {
if (current == 0) {
if (what instanceof TerrestrialAnimal) {
terrestrialAnimals = true;
}
}
if (current < maximum) {
if ( (terrestrialAnimals && what instanceof AquaticAnimal) ||
(!terrestrialAnimals && what instanceof TerrestrialAnimal)) {
throw new IncompatibleAnimals("This isn't gonna work!");
}
members[current] = what;
current++;
return true;
}
return false;
}
throws¶
This will not work yet. In methods where exception objects can be created and thrown, it should be indicated in the method header that exceptional things can happen in this method, which will have to be handled by the caller.
This indication can be done by listing in the method parameterization, after the parentheses closing the parameters, but before opening the method block, the exception objects that can be thrown in the method, divided by commas, after the keyword throws
. If you don't want to bother much with it, just print the parent class, i.e. Exception
. In this case, we don't need to list anything else, because all exception classes are derived from Exception
, so it can be any child class.
public boolean acceptIntoHerd(Animal what) throws IncompatibleAnimals {
if (current == 0) {
if (what instanceof TerrestrialAnimal) {
terrestrialAnimals = true;
}
}
if (current < maximum) {
if ( (terrestrialAnimals && what instanceof AquaticAnimal) ||
(!terrestrialAnimals && what instanceof TerrestrialAnimal)) {
throw new IncompatibleAnimal("This isn't gonna work!");
}
members[current] = what;
current++;
return true;
}
return false;
}
Finally, we have an exception object that we can throw and the code would compile. All that's left is to handle the possible exception at the call site. To do this, we'll need some new keywords:
try, catch, finally¶
try
- this keyword starts the protected region, the part of the code that can throw an exception; the methods that can throw an exception are inside the try block.
catch
- follows the try block; the exception handling block(s).
finally
- the block that is executed whether or not an exception is thrown. This is usually where we close files, network connections.
Let's get started: the Herd
class's acceptIntoHerd
method can no longer be called as is, since it can throw an exception if circumstances happen to be just right. Therefore, this should be put in a try
block:
import animals.*;
public class SimpleMain {
public static void main(String[] args) {
Animal first = new Whale("Charlie");
Animal second = new Chicken("Kotkoda");
Herd herd = new Herd(5);
try {
herd.acceptIntoHerd(first);
herd.acceptIntoHerd(second);
} catch(IncompatibleAnimals incompatibleAnimals){
System.err.println("Incompatible animals! Terrestrial and aquatic animals cannot mix!");
}
}
}
The possible exception can be caught with one of the catch
branches. There may be one or more of these. After the catch keyword, you can specify the type of exception you want to catch, and the name you want to use for it in the block responsible for handling the exception. When catching an exception, we can catch a special type or a more general type. If we don't want to fiddle with it too much, we can catch the exception parent type, an instance of the Exception
class, since it also catches its child types. You can write several exception handler blocks in a row if you want to handle the caught exceptions differently depending on the type.
try {
herd.acceptIntoHerd(first);
herd.acceptIntoHerd(second);
} catch(IncompatibleAnimals incompatibleAnimals){
System.err.println("Incompatible animals! Terrestrial and aquatic animals cannot mix!");
} catch(Exception exc){
System.err.println("There is an error! :(");
}
Exception
class, you have the option from Java 7 to use the following syntax:
try {
herd.acceptIntoHerd(first);
herd.acceptIntoHerd(second);
} catch(IncompatibleAnimals|ArrayIndexOutOfBoundsException exc){
System.err.println("There is trouble :(");
}
This roughly means that we catch either an instance of IncompatibleAnimals
or an instance of ArrayIndexOutOfBoundsException
and handle them the same way.
If there is a piece of code that we want to ensure runs anyway, we should put it in a finally
block.
try {
herd.acceptIntoHerd(first);
herd.acceptIntoHerd(second);
} catch(IncompatibleAnimals|ArrayIndexOutOfBoundsException exc){
System.err.println("There is trouble :(");
} finally {
System.out.println("This will run anyway!");
}
Videos¶
- Exceptions, concept (EAIJ): https://youtu.be/kKlUtP-tHqQ
- Try-catch (EAIJ): https://youtu.be/u1dpwDqIHG8
- Distinguishing multiple exceptions, split (EAIJ): https://youtu.be/MttMQnm452Y
- Own exception, throwing an exception (EAIJ): https://youtu.be/lllUkt6MerQ
Tasks¶
- Create a fixed size stack to store integers (using array) and implement push/pop operations.
- Write an executable class that expects "push" or "pop" statements from the console in the Main method. If it receives a pop command, execute it and write the extracted item to the console. For a push statement, an integer must follow, put it in the stack.
- Write an exception class that the above functions (push/pop) throw when the stack is full or empty.