Home Java Java-tips Flow Exceptions Exceptions - More

Ask Questions?

View Latest Questions

Advertisement


 
 

Exceptions - More
Posted on: July 26, 2006 at 12:00 AM
There are many exceptions, but they can be put into two groups: checked exceptions and unchecked exceptions

Java Notes

Exceptions - More

Exceptions | Exception Usage | Exceptions - More

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.

  • Unchecked Exceptions -- These exceptions are usually something that should have been prevented by more careful programming. For example, you should never get NullPointerException or ArrayIndexOutOfBoundsException. If you do, there is something wrong with your program, and you need to fix it. You usually don't catch unchecked exceptions. Instead, fix your program so it can't produce one of these. However, NumberFormatException is the one exception of this type that is usually caught.
  • Checked Exceptions -- These are usually errors in the input data. The programmer has no control over the input the user gives you, eg, file names, .... If the user gives you a bad value, it may cause an exception when you use it. You need to check for bad input using a try statement.

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.

  • Slow. Exceptions are very slow. When an exception occurs, the Java runtime system works its way up the call stack (you might be surprised at how deep this can get), identifying each source statement at which a call was made, and building a string that reports this. This string is then used in creating the exception object that is thrown. This is not fast.
  • Readability. Experienced programmers expect exceptions to have something to do with errors, so it is very uncomfortable to read code where it is used for normal flow. A programmer has to stop and examine the code in the try clause to see what might have caused the exception.

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.

Throwing predefined exceptions

It's common to test parameters of methods or constructors for legality. But if the value is illegal, what should you do? If your class is designed to be called by other, you should define your own exceptions, so you can document the errors for them more easily.

But if your methods are only being called by your own code, you can either use an assert or throw an exception. I often use something like the following, supplying a comment with the class, method, and meaningful comment.

if (age < 0) {
    throw new IllegalArgumentException("PhysicalExam.scaleBloodPressure: age is negative");
}

Note that this shouldn't be used for illegal user input -- that should have been checked in the interface. This should only be thrown if there is a programming error. This is only for your own use.

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.

Define library exceptions to tell programmer they should fix their code

If you write a library that is used by others, throw your own exceptions if your code is called with illegal values, eg, something isn't initialized. Your exceptions are a way to tell the applications programmer that they should fix their code.

Defining your own exceptions

You can also create your own exceptions. [Not yet written]

References

Copyleft 2006 Fred Swartz MIT License
Advertisement


DMCA.com