Java Notes

Exceptions - More

Prev: Exception Usage Next: Throwing Exceptions

Kinds of Exceptions

There are many exceptions, but they can be put into two groups: checked exceptions and unchecked exceptions. There is some controversy about which type you should use. A discussion of some of the issues can be found at Java theory and practice: The exceptions debate.

Use exceptions for exceptional conditions, NOT normal control flow

Probably most of your student programming has been "Happy Trails" style, where you didn't have to worry much about handling errors. But error handling is really a big deal in most real programs, and exceptions play a central role in dealing with errors.

All experienced programmers agree that using exceptions for normal processing flow is wrong. Exceptions should only be used only for errors or unusual conditions, and the equivalent if tests should be used for normal processing. There are good reasons for this.

Here are examples where beginning programmers used exceptions, but should NOT have. These are all done to either speed execution (which they do not) or simplify code (which they arguably do not).

Example: Test for end of array index range

GoodBAD
int[] a = new int[1000];
for (int i=0; i < a.length; i++) {
    a[i] = i;
}

You might wonder how inefficient this is because the loop must compare the index with the array size 1000 times, but only the final test is important. Because Java always checks the subscript range anyway, why not make use of its check?

int[] a = new int[1000];
try {
    for (int i=0; ; i++) {  // No range check.
        a[i] = i;
    }
} catch (ArrayIndexOutOfBoundsException e) {
}

Exceptions are so slow that this won't be faster unless the array is extremely large (much, much larger than 1000).

Avoiding edges in looking for adjacent array cells.

Problem: You must invert the values in a cell in a rectangular grid and its non-diagonally adjacent cells. The difficulty is in dealing with the edge cases, where you must avoid referencing non-existent adjacent cells.

Two alternate definitions of a method are given, one uses exceptions and the other uses if to handle the boundary violation cases. The exception solution is very inefficient and might be very hard to interpret by the reader. The difficulty is increased because the writer chose to use the Exception class instead of ArrayIndexOutOfBoundsException. The use of Exception suggests that it is designed to catch other exceptions too. if the body of the try had been larger, it might have been very difficult decide exactly which exception is being caught. Do you see which other exception could be thrown by the code, at least in principle?

private boolean[][] cell = new boolean[SIZE][SIZE];
. . .
// BAD 
public void flipA(int row, int col) {
          cell[col  ][row  ] = !cell[col][row];
    try { cell[col+1][row  ] = !cell[col+1][row  ];} catch(Exception e) {}
    try { cell[col-1][row  ] = !cell[col-1][row  ];} catch(Exception e) {}
    try { cell[col  ][row+1] = !cell[col  ][row+1];} catch(Exception e) {}
    try { cell[col  ][row-1] = !cell[col  ][row-1];} catch(Exception e) {}
}

// Much better (faster and less worrisome to the normal reader)
public void flipB(int row, int col) {
                      cell[col  ][row  ] = !cell[col  ][row  ];
    if (col < SIZE-1) cell[col+1][row  ] = !cell[col+1][row  ];
    if (col > 0     ) cell[col-1][row  ] = !cell[col-1][row  ];
    if (row < SIZE-1) cell[col  ][row+1] = !cell[col  ][row+1];
    if (row > 0     ) cell[col  ][row-1] = !cell[col  ][row-1];
}

Another solution to avoid edge cases is to define extra rows and columns of boundary cells, and translate the subscripts, thereby replacing the if tests with two additions. This requires translating subscript references in all methods. If the class is properly encapsulated, users of the class will not know about it.

private boolean[][] cell = new boolean[SIZE+2][SIZE+2];
. . .
public void flipC(int r, int c) {
    int row = r + 1;
    int col = c + 1;
    cell[col  ][row  ] = !cell[col  ][row  ];
    cell[col+1][row  ] = !cell[col+1][row  ];
    cell[col-1][row  ] = !cell[col-1][row  ];
    cell[col  ][row+1] = !cell[col  ][row+1];
    cell[col  ][row-1] = !cell[col  ][row-1];
}

Other examples

There are numerous cases in addition to subscription where the use of exceptions is entirely inappropriate.

Danger from the intermediate layers - finally

Exceptions provide a good infrastructure for error processing. The simplicity of throwing an exception at a deep level and catching it at a high level may generate problems at the intermediate, skipped, levels. Did any of these methods leave any part of a data structure or resource in an inconsistent state? Each place that this may be true of needs to enclose critical code in a try...finally block.

References