When we are writing code that detects and error, what should we do?
In some cases, it makes sense to tell the user of the program that there was an error. For example in assignments you have written, you have had code like this:
// get a positive value
int value;
do {
System.out.print("Enter a positive number: ");
value = scanner.nextInt();
if (value <= 0) {
System.out.println("Error, please enter a positive number.");
}
} while(value <= 0);
This code checks for an error condition, and reports any errors to the user of the program.
However, oftentimes the "user" of the code we write is not the person who is running a program, but another programmer who is using something we wrote. Most code is not used directly by end users, but rather used by other programmers as part of something else.
Exceptions provide a way for our code to signal errors to other parts of a program.
An exception is a programming construct specifically for handling errors.
There are three parts of an exception:
Try
Specifies a block of code in which an exception might occur. Code in the try block is executed until an exception happens.
Catch
Specifies a block of code to handle an exception in some way. Code in the catch block is only executed after an exception occurs.
Throw
Creates a new exception, and transfers control to the nearest catch block that can be found. Exceptions can only be caught by a catch matching its type.
Exceptions are created with the "throw" keyword in Java. For example, the following program throws an exception to avoid a divide by zero.
public class Slope {
public static double calculateSlope(int x0, int y0, int x1, int y1) {
// avoid dividing by zero
if(x1 == x0) {
throw new ArithmeticException("Vertical lines can not have a slope");
}
return (double)(y1 - y0) / (double)(x1 - x0);
}
public static void main(String args[]) {
// ok
double slope1 = calculateSlope(3, 4, 5, 6);
System.out.println(slope1);
// exception!
double slope2 = calculateSlope(3, 4, 3, 6);
}
}
In the program above, the exception halts the program. If we want the program to recover from the exception, we must catch it.
To catch an exception, we use a try/catch block. The form of this is as follows:
try {
// code that can cause an exception
} catch(type value) {
// handle any exception here
}
Whenever an exception is thrown, the program will search for a catch block that catches that type of exception. When it finds one, that code is executed - the code that caused the exception is abandoned.
The goal of the catch block is to try to handle the problem as best as we can. This depends on the situation. Sometimes we may want to halt the program, sometimes we may want to try something different and sometimes we may want to just ignore the problem.
In this example, we handle the problem of a vertical slope by printing a message stating that the line is vertical.
double x0 = in.nextDouble();
double x1 = in.nextDouble();
double y0 = in.nextDouble();
double y1 = in.nextDouble();
try {
double slope = calculateSlope(x0, y0, x1, y1);
System.out.println("The slope of the line is " + slope);
} catch(ArithmeticException e) {
// handle the exceptional case
System.out.println("Line is vertical!");
}
}
The big reason to use exceptions here is that it allows the calculateSlope
method to be used in other programs that handle the problem differently. Exceptions
decouple detecting a problem from handling it.
In the example above, the exception type we threw was ArithmeticException. When Java looks for a suitable catch block, it searches up the stack of method calls until it finds a matching catch.
Java comes with some different types of exceptions built-in that we can use.
Another common ones we can use is IllegalArgumentException
which
can be used when bad values are passed into a method.
We can also create out own types of exceptions, which we can discuss after talking about inheritance.
When we catch an exception, we are also passed the exception object into the catch block. With this, we can get some information out of it. Every exception has a "message" in it which we can print out. For example, if we catch an index out of bounds exception, the message will tell us the length of the array and also what index we tried to use:
for (int i = 0; i <= array.size(); i++) {
try {
array[i] = 5;
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
This will produce a message like the following:
Index 100 out of bounds for length 100
We can also print the stack trace of an exception, which is where we are in the method calls of a program at the point in which the exception occurred.
For example, you in this program the exception occurs in the method
fillArray
which is called from main. The exception is
caught in main, and the stack trace shows us what happened:
public static ArrayList<Integer> fillArray() {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(i * 10);
}
list.set(100, 0);
return list;
}
public static void main(String[] args) {
try {
ArrayList<Integer> list = fillArray();
} catch (Exception e) {
e.printStackTrace();
}
}
When we run this the stack trace is printed:
java.lang.IndexOutOfBoundsException: Index 100 out of bounds for length 100 at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100) at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106) at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302) at java.base/java.util.Objects.checkIndex(Objects.java:359) at java.base/java.util.ArrayList.set(ArrayList.java:441) at Main.fillArray(Main.java:10) at Main.main(Main.java:17)
Generally, printing out the messages and stack traces of exceptions you caught are very handy for debugging, but usually don't make as much sense in finished programs.
Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.