Exceptions are exceptional events that disrupt the normal flow of a program.
An exception is thrown when a method hands an exception object to the runtime system; the exception object contains information about the error that caused the exception to be thrown.
The exception can be caught by an exception handler. An exception handler is a block of code that receives the exception object after it is thrown.
If there is no appropriate handler for the exception that was thrown, the program will stop execution in a largely uncontrolled fashion (it will crash).
Java uses objects to represent exceptions. Like all other Java
objects, the hierarchy is rooted at Object
:
Throwable
Throwable
is the superclass for all exception and error objects
in Java. Only objects that are substitutable for Throwable
may be thrown or caught.
Throwable
defines all of the methods for exceptions
and errors. In fact, if you look at the APIs for its subclasses,
you will find no new methods for the subclasses.
Throwable
Throwable
defines several methods to access the information
about the error or exception. There are two you should know about for
CSE1020:
String getMessage()
Returns the detail message string of this throwable.
try
{
String s = "hello";
s.substring(s.length() + 2);
}
catch (IndexOutOfBoundsException ex)
{
System.out.println(ex.getMessage());
}
The above code fragment prints:
String index out of range: -2
Throwable
void printStackTrace()
Prints this throwable and all of the methods that were invoked
to cause the throw.
try
{
String s = "hello";
s.substring(s.length() + 2);
}
catch (IndexOutOfBoundsException ex)
{
ex.printStackTrace();
}
The above code fragment prints:
java.lang.StringIndexOutOfBoundsException: String index out of range: -1 at java.lang.String.substring(String.java:1938) at java.lang.String.substring(String.java:1905) at Ex.main(Ex.java:7)
Error
An
Error
represents an exceptional condition that is
external to the application, and that the application usually cannot
anticipate or recover from. An example would be hardware failure
(hard drive failure, out of memory, etc.).
There usually isn't anything the programmer can do if an
Error
is thrown; thus, it usually makes no sense to
attempt to catch Error
s and the compiler does not
force the programmer to catch them.
Exception
An
Exception
object represents an unusual event
that is internal to the application (ie. something is wrong
with the Java code). There are two kinds of exceptions: checked
and unchecked.
All exceptions, except subclasses of Error
and RuntimeException
, are checked exceptions.
Probably most of the exceptions you have seen in your
own code are unchecked exceptions.
Checked exceptions are unique to Java, and they are somewhat controversial. Other programming languages that use exceptions use unchecked exceptions.
A checked exception is one that every well-written program should anticipate and handle.
The Java compiler enforces a Catch or Specify requirement whenever you use a method that may throw a checked exception. Your code must either:
try
block that has a catch
block that
can handle the exception, or
main
method must declare
that it throws the exception
You have already seen one common example of a checked exception.
Whenever you previously created a Scanner
for a
File
object you
had to alter the header of your main
method:
public static void main(String[] args) throws IOException { Scanner fileInput = new Scanner(new File(someFileName)); }
If you look at the
Scanner
API, you will see that
its constructor advertises that it throws a
FileNotFoundException
, which is a checked exception.
If someFileName
is the name of a non-existant file,
the constructor call will throw an exception that the programmer
must handle.
Any method or constructor that throws a checked exception must declare so in its header:
public Scanner(File source) throws FileNotFoundException
Constructs a new Scanner that produces values scanned from
the specified file...
Parameters:
source
- A file to be scanned
Throws:
FileNotFoundException
- if source is not found
RuntimeException
The third kind of exception is the runtime exception.
These are exceptional conditions that are internal to the application,
and that the application usually cannot anticipate or recover from.
These usually indicate programming bugs, such as logic errors or
improper use of an API.
—The Java Tutorials: Exceptions
Runtime exceptions help the programmer find programming errors in their code by telling the programmer where the exception has occurred and the sequence of methods that caused the exception to occur.
RuntimeException
and all of its subclasses are
unchecked exceptions.
An unchecked exception is an exception that the compiler does not force the programmer to handle. Forcing the programmer to catch every possible runtime exception even for perfectly written code would be very tedious and would reduce the clarity of the program. Because unchecked exceptions are caused by programming errors, they can be eliminated by careful design, implementation, and debugging.
output.println("Enter a string : "); String userInput = input.next(); // throws StringIndexOutOfBoundsException // if s.length() < 5 String s = userInput.substring(5); // throws NumberFormatException // if userInput is not an integer Integer i = Integer.parseInt(userInput); // throws ArithmeticException // if i == 0 int quotient = 5 / i;
Methods that throw unchecked exceptions may or may not declare so in their headers.
String.charAt
may throw an
IndexOutOfBoundsException
but it does not declare so in its header.
Integer.parseInt
may throw an
NumberFormatException
and it declares so in its header.
It is common to have a block of code that contains many
method invocations that could throw exceptions. Also, many
methods can throw more than one kind of exception. In such
circumstances, you can use more than one catch
block.
You need to remember three things when using multiple
catch
blocks:
catch
blocks are scanned in the order
that they appear, and the first block that can handle the thrown
exception is chosen as the handler.catch
blocks must be reachable; if not,
then the compiler will issue an error.try
block.
Doing so leads to a compiler error.catch
blocks are scanned in orderBecause exceptions are objects, substitutability comes into play for exception handlers. Consider the following inheritance relationship:
and the following program ExSub.java
:
import java.io.PrintStream; public class ExSub { public static void main(String[] args) { PrintStream output = System.out; try { char c = args[0].charAt(2); } catch (StringIndexOutOfBoundsException e) { output.println("Whoops, string is too short"); } catch (IndexOutOfBoundsException e) { output.println("Whoops, no command line arguments"); } } }
catch
blocks are scanned in orderIf the program is run like so:
java ExSub
then there are no command line arguments (args
has length zero) and attempting to access any element
in args
results in an
ArrayIndexOutOfBoundsException
being thrown.
The first try
block is examined to see
if it can handle the exception:
try
{
char c = args[0].charAt(2);
}
catch (StringIndexOutOfBoundsException e)
{
output.println("Whoops, string is too short");
}
catch (IndexOutOfBoundsException e)
{
output.println("Whoops, no command line arguments");
}
catch
blocks are scanned in order
ArrayIndexOutOfBoundsException
is not
substitutable for StringIndexOutOfBoundsException
,
so the second try
block is examined:
try
{
char c = args[0].charAt(2);
}
catch (StringIndexOutOfBoundsException e)
{
output.println("Whoops, string is too short");
}
catch (IndexOutOfBoundsException e)
{
output.println("Whoops, no command line arguments");
}
catch
blocks are scanned in order
ArrayIndexOutOfBoundsException
is
substitutable for IndexOutOfBoundsException
,
so control flows to the handler:
try
{
char c = args[0].charAt(2);
}
catch (StringIndexOutOfBoundsException e)
{
output.println("Whoops, string is too short");
}
catch (IndexOutOfBoundsException e)
{
output.println("Whoops, no command line arguments");
}
catch
blocks must be reachable
Substitutability affects the order in which you must place the
catch
blocks. What happens if we reverse
the order the handlers?
// THIS WILL NOT COMPILE try { char c = args[0].charAt(2); } catch (IndexOutOfBoundsException e) { output.println("Whoops, no command line arguments"); } catch (StringIndexOutOfBoundsException e) { output.println("Whoops, string is too short"); }
catch
blocks must be reachable// THIS WILL NOT COMPILE try { char c = args[0].charAt(2); } catch (IndexOutOfBoundsException e) { output.println("Whoops, no command line arguments"); } catch (StringIndexOutOfBoundsException e) { output.println("Whoops, string is too short"); }
Because of Rule 1 (blocks are scanned in order),
all exceptions substitutable for
IndexOutOfBoundsException
will be caught by the first block;
unfortunately, StringIndexOutOfBoundsException
is one such exception.
We say that the StringIndexOutOfBoundsException
handler
is unreachable, and the compiler will issue a compilation error.
As a programmer, you must deal with all checked exceptions, but you cannot include a handler for an checked exception that is not thrown:
// THIS WILL NOT COMPILE
try
{
char c = args[0].charAt(2);
}
catch (IOException e)
{
}
All IOException
s are checked exceptions, but
no method in the try
block can throw such
an exception. The compiler will flag this as a compilation error.
Note that Java does not require unchecked exceptions to appear
in the method header so there is no way for the compiler to
verify if a method throws an unchecked exception; thus, you can
catch whatever unchecked exception you want, even if the code
in the try
block can never cause such an exception
to occur.