Section B.2 Exceptions
Consider this program:
If you enter a non-number the program crashes:
Enter index 0-3: two Exception in thread "main" java.util.InputMismatchException at java.base/java.util.Scanner.throwFor(Scanner.java:939) at java.base/java.util.Scanner.next(Scanner.java:1594) at java.base/java.util.Scanner.nextInt(Scanner.java:2258) at java.base/java.util.Scanner.nextInt(Scanner.java:2212) at ErrorProne.main(ErrorProne.java:11)
The lines beginning with
at
are a stack trace. They show the chain of method calls in reverse chronological order with the file name and line number. You’ll want to look for the one that is in your program. In this case, the error was in ErrorProne.java:11
, where the 11
is the line number in the source file with the nextInt
call.If you enter an index outside the array bounds, the program crashes (the output has been reformatted to fit on the line length of this page):
Enter index 0-3: 5 Enter number to divide by: 0 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 4 at ErrorProne.main(ErrorProne.java:21)
And if you enter a zero as the divisor, you get yet another error:
Enter index 0-3: 2 Enter number to divide by: 0 Exception in thread "main" java.lang.ArithmeticException: / by zero at ErrorProne.main(ErrorProne.java:21)
All of these errors are called exceptions—exceptional conditions after which the program cannot continue to run. In Java, we say that the program throws an exception when it fails.
You already know how to handle these problems before they crash your program: you can use an
if
statement with Scanner
’s hasNextInt
method to make sure that the user enters an integer. You can use if
statements to check that the index number is between 0 and the array’s length, and that the divisor is non-zero.
Subsection B.2.1 try
and catch
In addition to using an
if
statement to avoid errors, Java has another general mechanism for catching exceptions before they stop your program: try
and catch
. Let’s enclose the code that could have an error in a try
block:The
try
block is followed by a catch
block that specifies the exception we want to handle and how to handle it. Let’s start with the division by zero, which generated a java.lang.ArithmeticException
:If you recompile and run the program and enter 2 and 0 as your numbers, you’ll get the error message in the
catch
block. Notice that the printf
statement after the division doesn’t occur—when an exception is thrown, execution imediately jumps to the catch
.If you enter
five
or 5
for the first input, you’ll still get the NumberFormatException
or ArrayIndexOutOfBoundsException
.You may follow a
try
block with as many catch
blocks as you want. Let’s add two more catch
blocks to handle these other two errors:The variable in parentheses after
catch
is local to the catch
block. This means you can use the same variable name in all the catch
blocks, and, by convention, most programmers name it ex
. (We will put it to use later in the chapter.)When an exception occurs, Java goes through the
catch
blocks in the order that they appear in your program and finds the first one that applies. In the preceding example, we could have put the catch
blocks in any order. However, the order does become important once we examine the hierarchy of exceptions.Subsection B.2.2 The Hierarchy of Exceptions
All exceptions descend from the
Exception
class. This list shows many of the most common exceptions you will encounter when learning Java; each category contains many other classes:Exception
IOException
FileNotFoundException
RunTimeException
ArithmeticException
IllegalArgumentException
IllegalFormatException
InvalidParameterException
NumberFormatException
IndexOutOfBoundsException
ArrayIndexOutOfBoundsException
StringIndexOutOfBoundsException
NullPointerException
If you put a
catch
for a parent class before a catch
for a child class, the parent class will catch the error. Thus, in this code fragment:You will see the “Something unexpected occurred.” message. For this reason, always
catch
the more specific (child) exception classes before you catch
the more general (parent) exception classes.Let’s say you
catch
the most generic Exception
possible, or one that could have many possible causes, such as FileNotFoundException
. How can you give the user more information than just “something unexpected occurred”? You can use the variable that you declared in the catch
clause. Here are some methods that you can use1
These methods are from the
Throwable
class, which is the parent of all Java exceptions.getMessage()
- Returns a detailed message string
toString()
- Returns a short description
printStackTrace()
- This
void
method prints the exception and its stack trace to the standard error stream, which is your terminal window
For example, you could
catch
only Exception
and use one of these methods to tell users what went wrong:You can see this in action in file ErrorProneGeneralException.java in the code repository. As you can see, the error messages are not as satisfactory as those you would write yourself when handling the specific exceptions.
Subsection B.2.3 The finally
Clause
Sometimes programs need to take an action whether the code in the
try
block succeeded or not. (For example, if you have allocated resources and want to “clean up” before exiting the program.) This is the role of the finally
clause. It is executed whether the try
block succeeded or an exception was caught by a catch
block. In fact, it is executed even when there is a return
or break
in a block:This program will print
In the finally clause.
even though the return
prevents the last println
from happening.Subsection B.2.4 Throwing Exceptions
Before proceeding, let’s ask a philosophical question: why do these exceptions even exist? Why doesn’t the JVM simply do something reasonable when it encounters one of these situations? That’s the whole problem—what does “reasonable” mean? For dividing by zero, some programs might want to return a zero as a default answer. Other programs might want to print an error message and end the program. Still others might want to detect the attempt to divide by zero and ask for new input.
Because the definition of “reasonable” is different for every program and every programmer, it makes sense for these exceptions that happen in many different circumstances to throw the problem back to the programmer and say, “you handle this.”
If you are writing a library of Java methods for other people to use, you won’t be able to anticipate all of your users’ needs either. Your methods will also need to
throw
an Exception
so that the people who use your methods can handle errors as they see fit.For example, let’s say you have a Java library with a method that calculates the average of an array of
double
values. If somebody hands you an empty array, that’s an illegal argument, and you can write your code to throw
an IllegalArgumentException
:In line 3, specify that this method
throws
an IllegalArgumentException
.In line 12, when the length of the array is zero, use the keyword
throw
and create a new IllegalArgumentException
. The argument to the constructor is the value that will be returned when someone calls the exception’s getMessage
method.Here’s an example that calls the code (without
try
and catch
) and the resulting error, formatted to fit the page:Exception in thread "main" java.lang.IllegalArgumentException: Empty array at ArrayStats.average(ArrayStats.java:12) at TestArrayStats.main(TestArrayStats.java:5)
If your method can throw more than one exception, you list them separated by commas in the
throws
clause:Subsection B.2.5 Checked and Unchecked Exceptions
All of the exceptions used in the preceding examples (and all exceptions that are descendants of
RuntimeException
) are called unchecked exceptions. Java doesn’t require you to enclose operations that cause such exceptions in a try
-catch
block. This is a good thing, or you’d need try
-catch
blocks around every division, array access, and string-to-numeric conversion.For many operations that could throw an unchecked exception, you are better off using an
if
statement to avoid the error in the first place. You can, for example, use an if
to check if an index is within array bounds, a divisor is non-zero, or if the user has actually entered data that can be converted to integer. This also gives you greater control over the program flow and structure. See, for example, the NormalErrorChecking.java file in the code repository.So, why are we talking about
try
and catch
at all? Because there are some exceptions that can’t easily be handled by an if
-else
. These exceptions are called checked exceptions. The Java compiler requires you to enclose operations that might throw these exceptions in a try
block and provide a catch
block to handle them. You are also required to list them in a throws
clause if you are throwing those exceptions yourself. Foremost of these checked exceptions is the IOException
, generated by I/O (Input/Output) operations. When you are working with files in Java, you will need to check this exception. Handling files is the topic of the next section of this chapter.You have attempted of activities on this page.