The traditional solution to dealing with exceptional cases
is to distinguish normal values from exceptional values, and
to require every piece of code to check for exceptional
values. However, this is neither robust nor manageable,
even in modestly sized systems, nor is it appropriate for
processing asynchronous errors.
Exceptions provide a control-flow mechanism that is quite
different from other control-flow constructs found in common
languages.
Exceptions provide a form of non-local jump where the
destination is unknown: we must provide an exception name
and the runtime system will jump to the most recent
exception handler block that we are currently inside.
In addition, the runtime system may:
-
Pass information with the jump, e.g., passing a subclass
of
Throwable
in Java.
-
Execute code in between throwing the exception and
catching it in the exception handler, e.g., executing
finally
blocks in Java.
Some examples:
|
00001: class E1 extends Exception
00002: {
00003: E1 () {
00004: }
00005: }
00006:
|
|
|
00001: class E2 extends RuntimeException
00002: {
00003: E2 () {
00004: }
00005: }
00006:
|
|
Pass through many activation records and reach the
top-level exception handler, as well as checked vs
unchecked exceptions:
|
00001: class Test1
00002: {
00003: public static void main (String[] args)
00004: throws E1
00005: {
00006: int n = Integer.parseInt (args[0]);
00007: foo (n);
00008: }
00009:
00010:
00011: static void foo (int n)
00012: throws E1
00013: {
00014: if (n == 0) {
00015: throw new E1 ();
00016: } else if (n == 1) {
00017: throw new E2 ();
00018: } else {
00019: foo (n - 2);
00020: }
00021: }
00022: }
00023:
|
|
More complex control flow:
|
00001: import java.io.FileWriter;
00002: import java.io.IOException;
00003: import java.io.PrintWriter;
00004:
00005:
00006: class Test2
00007: {
00008: public static void main (String[] args)
00009: {
00010: double failureProbability = Double.parseDouble (args[0]);
00011: int numLines = 0;
00012:
00013: try {
00014: FileWriter fw = new FileWriter ("output.txt");
00015:
00016: try {
00017: for (; numLines < 30; numLines++) {
00018: fw.write (generateLine (failureProbability) + "\n");
00019: try {
00020: Thread.sleep (50);
00021: } catch (InterruptedException e) {
00022: // Ignore InterruptedException.
00023: }
00024: }
00025: } catch (E1 e) {
00026: System.err.println ("Caught E1 exception from generateLine");
00027: } catch (IOException e) {
00028: System.err.println ("Unable to output to file");
00029: } finally {
00030: try {
00031: fw.write ("Number of lines written: " + numLines + "\n");
00032: fw.close ();
00033: } catch (IOException e) {
00034: System.err.println ("Unable to write summary entry and close file");
00035: }
00036: }
00037:
00038: } catch (IOException e) {
00039: System.err.println ("Unable to open file");
00040: e.printStackTrace ();
00041: }
00042: }
00043:
00044:
00045: static String generateLine (double failureProbability)
00046: throws E1
00047: {
00048: // Model failure with probability failureProbability.
00049: if (Math.random () < failureProbability) {
00050: throw new E1 ();
00051: }
00052:
00053: return "Log entry [" + System.currentTimeMillis () + "ms]";
00054: }
00055: }
00056:
|
|
Finally blocks always executed:
|
00001: class Test3
00002: {
00003: public static void main (String[] args)
00004: throws E1
00005: {
00006: try {
00007: double failureProbability = Double.parseDouble (args[0]);
00008: if (Math.random () < failureProbability) {
00009: throw new E1 ();
00010: }
00011: } finally {
00012: System.out.println ("Executed finally block");
00013: }
00014: }
00015: }
00016:
|
|
Finally blocks always executed, even in odd situations:
|
00001: class Test4
00002: {
00003: public static void main (String[] args)
00004: throws E1
00005: {
00006: for (int i = 0; i < 5; i++) {
00007: if (true) {
00008: continue;
00009: }
00010: System.out.println ("Executed first block");
00011: }
00012:
00013: for (int i = 0; i < 5; i++) {
00014: try {
00015: continue;
00016: } finally {
00017: System.out.println ("Executed second block");
00018: }
00019: }
00020: }
00021: }
00022:
|
|
There are many tricky details:
-
Java forces you to declare checked exceptions. The
unchecked exceptions are subclasses of
RuntimeException
or Error
, e.g.,
you need need not declare ArrayStoreException
or SecurityException
everywhere.
-
What happens if we evaluate
f (e1, e2)
,
e2
has a side-effect, and e1
throws an exception? Is there a guaranteed order of
evaluation?
-
Java exceptions are heap-allocated objects and can contain
arbitrary information. In particular, a stack trace is
extremely useful.
-
APIs with possible exceptions in the method signatures is
handy for static analysis, but it can be restrictive for
implementations. For this reason, Java has added "cause"
exceptions, where one exception wraps another.
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Exception.html#Exception(java.lang.Throwable)