CM10228 / Programming Ib:   Lecture 7


Errors, Exceptions & Other Strange Ways to Get Around


I. Nonlinear Control of Flow

  1. Somewhere, for any executing program, there is a special references that says what statement is about to be executed next: the program counter.
  2. Programmers (people in general?) think best about sequences simple plans, always go to the next step.
  3. Loops are a little trickier to think about, because they make the pointer skip around (although in a very determined way.)
  4. In the beginning, before loops & subroutines, there was GOTO
    1. use a line number of the code
    2. more sophisticated, use a label
    3. Used to create iteration, subroutines.
    4. Assembly code is still like this.
  5. Example: 
    mysum = 0
    myiterator = 0
    LOOP:
    if myiterator == 5
    goto END
    mysum += myiterator
    myiterator += 1
    goto LOOP
    END:
    print "mysum = " mysum
  6. Once loops & function calls had been invented, non-linear control was considered inelegant / bad style.
    1. Generally, this is true.  People have more trouble reading code if it doesn't go in a sequence.
    2. Loops are self-documenting
      1. the best ones (for in C, do in lisp) tend to tell you what their criteria & index are in the declaration.
      2. they all clearly demarcate where they begin and end (with brackets and/or indentation)
    3. Function/method calls are also self-documenting, they are abstract & their names should tell you what they do.
    4. However, this has always been a little controversial.
    5. Sometimes making a loop do exactly what you want is just so messy that jumping out of it might be better.
    6. But in general, programmers who use GOTO use it because they haven't thought hard enough about the alternatives, so GOTO is widely condemned.
    7. I am sure someone in this department will think I shouldn't even have mentioned the notion to you!
    8. But Apple clearly still uses GOTO... or haven't you heard of the Apple GOTO #fail?
  7. Java has a number of ways of going non-linear
    1. break & continue,
      1. only work in loops
      2. will accept labels as arguments.
    2. return: can happen anywhere in a method call, skips the rest of the code.
    3. error/exception handling (today's main topic).
    4. thread handling (we'll do this tomorrow).
  8. Quickly, here's what break & continue do:
    1. break ends a loop & transfers the flow of control to whatever comes next.
    2. continue returns to the top of a loop, skipping any remaining code in the loop.
    3. Either command can take an argument
      1. lets you disambiguate which loop you want to break or continue if you are in nested loops.
      2. Labels are similar as above, you just stick a name in with a colon after it, but it must be right before a loop starts.
    4. Here's a page from Sun with some examples:

II. Signals & Errors

  1. One reason that you might want to go non-linear is if something else interrupted your program.  Examples:
    1. The machine is being shut down.
    2. Someone wants to chat to you (this is now usually handled by the OS/windowing system).
    3. You want the program to do something every five minutes (like save it's current state in a file on the hard disk.)
    4. Someone killed your window with a mouse.
  2. Here's a list of signals from the "man kill" page on unix:
         Some of the more commonly used signals:
         1       HUP (hang up)
         2       INT (interrupt)
         3       QUIT (quit)
         6       ABRT (abort)
         9       KILL (non-catchable, non-ignorable kill)
         14      ALRM (alarm clock)
         15      TERM (software termination signal)

  3. Don't usually want the program to just die exactly when the event occurs!
    1. Probably want to save state.
    2. Always want to:
      1. close files --- make sure disk is safe,
      2. kill children / spawned processes (e.g. dependent sub-windows, like "find").
    3. May want to go back to what you were doing after you've handled the situation.
  4. These problems can always happen, not controversial that you should be able to handle them.
  5. Common metaphor in many programming languages is catching & throwing.
    1. Something "throws" an exception when an event happens.
    2. Something else needs to catch it!

III. Throwables

  1. One reason Java is cool, is that it actually made the thing you throw into an object. 
    1. In previous languages e.g. C you "throwing an exception" was sort of an expression (also said "raising an exception").
    2. All that got passed around was integers, which had meanings you could look up (called signals or flags: show % man kill).
  2. In Java, things that can throw are subclasses of java.lang.Throwable
  3. Things that are Throwable are either Exceptions or Errors.
  4. Errors tend to be things you can't do much about, so people don't tend to check for them in the code
    1. Either things that come from outside that you can't fix, or things that are really sort of glorified syntax errors. 
    2. Examples (from 1.4.1)
      AbstractMethodError ClassCircularityError ClassFormatError IllegalAccessError IncompatibleClassChangeError InstantiationError InternalError NoClassDefFoundError NoSuchFieldError NoSuchMethodError OutOfMemoryError StackOverflowError ThreadDeath UnknownError UnsupportedClassVersionError VerifyError VirtualMachineError
    3. Sun now has these nested in hierarchies, see e.g. VM Error http://docs.oracle.com/javase/7/docs/api/java/lang/Error.html
  5. Exceptions are things you might want to do something about.
    1. http://docs.oracle.com/javase/7/docs/api/java/lang/Exception.html
  6. Exceptions come in two types:
    1. checked:  you are obligated to do something about them or the compiler won't let you compile.
    2. unchecked:  you can look for them if you like, it's up to you.
  7. What's the difference?
    1. Some kinds of bad things will happen for exogenous reasons the programmer can't control, but can anticipate.
      1. e.g. not being able to write to a disk --- could be full.
      2. These are checked exceptions.
    2. Some kinds of bad things will only happen if a programmer makes a mistake.
      1. e.g. asking for an illegal index for an array.
      2. These are unchecked exceptions.
  8. All unchecked exceptions are a subclass of RuntimeExceptions (which is a subclass of Exceptions.)
    1. Examples: ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException, ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFormatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException
  9. Arguable whether you should check for unchecked exceptions:
    1. Do you expect other programmers who don't read your comments carefully to use your code?
    2. Do you think you might make a mistake while you are programming?
    3. Do you think the user might make a mistake that might break your code?
    4. Rather than checking for everything, often specific checks are inserted during development / debugging, or based on personal experience of the individual programmer.
  10. With checked exceptions,  you can actually get away with very little.
    1. The least you can do is to declare that the method throws the exception, thus passing the problem on to the next calling method.
    2. If nothing ever actually checks it, then when the exception gets triggered you will crash the virtual machine.
    3. The compiler makes sure you know this can happen (generally considered unprofessional to crash the VM.)
    4. More about this below.

IV. Catching Throwables

  1. The actual catching (and throwing) is done in syntax, not with objects.
  2. Keywords:
    try {}
    catch {}*
    finally {}.
  3. Any checked exception must be performed within a try clause.
  4. Any try clause must be followed by one or more catch clauses, each for a particular error.
    1. Order matters -- the first applicable catch phrase is applied.
    2. So you can (should?) finish with a very general one
    3. e.g. here's a piece of a program which is letting the user type an array index to access.  Whatever they typed in is in "myNumString", which is currently just a string.  This code tries to convert it into an integer.
      try {
      newIx = Integer.decode(myNumString).intValue();
      } catch (NumberFormatException nfe) {
      System.out.println("please only enter integers!");
      newIx = 0; // give newIx some value that won't stop the program
      } catch (Exception ex) {
      System.out.println("Wow, I didn't expect a " + ex);
      ex.printStackTrace(); newIx = 0;
      }
    4. Exceptions and NumberFormatExceptions are both classes.
    5. nfe & ex are both objects!
    6. ex won't be instantiated if nfe is:  only one catch clause runs.
    7. Have a look at the Java Doc to see what you can do with these objects.
    8. Printing their stack trace is very useful, it tells you what the call history was.
    9. Other ways to handle exceptions:
      1. Stop everything: System.exit(1);  Generally considered bad form!
      2. Throw a new exception of a type of your own making (see below.)
  5. Finally clauses are optional, run after ALL the other clauses.
    1. Allows you to have something that happens regardless of whether any of the catches is called.
    2. You can see one of these in the program I hopefully showed you running in class, which is just below.
    3. Even runs after return, continue or break are called!!
  6. Note that this program has many examples of bad form in it.  But it is just a hack to demonstrate some ideas.
    1. You shouldn't be afraid to hack, especially when you are trying to understand something for the first time.
    2. But you should know the difference between hacking and programming.
    3. You should only submit programs for your coursework, not hacks!
    4. This is the same program we looked at the declarations for in Lecture 6, this time we're looking at a different part of it: error handling.  And we're watching it run in eclipse!
/**
* @author joanna
*
* Illustrates how to catch exceptions, as well as command line I/O.
*
*/
public class BeBadIndex {// bunch of stuff skipped, see lecture 6 version

public static void main(String[] args) {
ArrayList myStuff = new ArrayList();

myStuff.add(new Integer(5)); // will be the 0th element.
myStuff.add(new Integer(7));
myStuff.add(new Integer(13));
myStuff.add(new Integer(2));


int newIx = -1;
try {
BufferedReader stdin =
new BufferedReader(new InputStreamReader(System.in));
String myNumString;

System.out.print("Welcome to my program. The prompt looks like >." +
"\n >> ");
while ((myNumString=stdin.readLine()) != null) {
try { // this is the bit you saw earlier...
newIx = Integer.decode(myNumString).intValue();
} catch (NumberFormatException nfe) {
System.out.println("er, you were meant to put in integers! Maybe I need more documentation.");
newIx = 0; // give newIx a value that won't crash the program
} catch (Exception ex) {
System.out.println("Wow, I didn't expect a " + ex);
} finally { // this prints out whether there's an error or not!
System.out.println("I have " + newIx + " as my index.");
System.out.flush();
}
try {
System.out.println("The value at " + newIx +
" is " + myStuff.get(newIx));
} catch (IndexOutOfBoundsException iobe) {
System.out.println("We're sorry, " +
"there is no such array element.");
}

System.out.print("\n >> ");
} // while reading
} catch (IOException ioe) {
ioe.printStackTrace();
System.exit(-3);
}
}
}


V. Throwing Throwables

  1. Syntax is pretty obvious (note -- java hashes won't throw an exception for this, that's why we might have to if we don't want null hash keys!):
    if (key == null) {
    throw new NullPointerException("null key in someBadPlaceForNulls");
    } else {
    return myHash.get(key)
    }
  2. Notice you have to create a new instance of the throwable object.
  3. String offers some diagnostics, will print if the object is printed.
  4. Should also declare this in documentation to warn programmers who use your class!  @throws will wind up in the java doc.
    @throws NullPointerException if the key is null
  5. If the exception is checked, then you must declare it in the method header:
    public void saveToFile(String myFileName)
    throws IOException
  6. Sometimes you just rethrow (propagate) an error you've caught:
    1. Satisfies the obligation for checked error.
    2. Allow calling function to handle.
    3. This is done automatically, implicitly with unchecked errors that aren't handled otherwise.
    4. Generally prefer the implicit handling of unchecked errors, makes code easier to read.
  7. Sometimes you may want to catch one error & throw another,
    1. e.g. to translate from a database layer of a program up to the GUI interface.
  8. Can define new exception classes.
    1. Allows for more information / diagnostics /  documentation.
    2. But can be confusing -- proliferation of names.
    3. Be sure to subclass off of something appropriate!

VI. Summary

  1. Program execution can jump around.
  2. In Java (and most modern languages) the way it can jump is severely limited & controlled in order to make code easier to understand.
  3. Java has exceptionally good error handling (that's the generic term, though you usually actually handle what Java calls "exceptions").

page author: Joanna Bryson
23 February 2015