CS410, Summer 1998 Lecture 4 Outline Dan Grossman Goals: * Sentinels (didn't get to them last time) * An object-oriented approach to variant data definitions (and fake it in C) * Exceptions, when to use them, and how to do it in Java (and fake it in C) Reading: Your favorite Java or OO programming books. (Sentinels are in CLR 11). * Sentinels: Sometimes by wasting one tiny bit of space per data structure we can gain coding clarity and/or constant factors of efficiency. Example of clarity: Doubly-linked list with sentinel: class DLL { private DLLNode sentinel; public DLL () { sentinel = new DLLNode(); // its val is irrelevant! sentinel.next = sentinel.prev = sentinel; } void insert(Object obj) { DLLNode n = new DLLNode(obj); n.next = sentinel.next; n.prev = sentinel; sentinel.next.prev = n; sentinel.next = n; } void delete (DLLNode n) { // it won't be the sentinel. n.prev.next = n.next; n.next.prev = n.prev; } //... other methods, eg. find, should never return the sentinel. } Example of constant factor efficiency: Character buffers: Explain in English (actual code has a lot of gunk) while (not end of word) { if (at halfway point) read in second half else if (at end point) read in first half move forward one character } with sentinels: while (not end of word) { if (current position is the special character) { figure out whether at halfway or end and read in more } move forward one character } Notice how the common case (not needing to read more in) now has one if instead of two. * An Object-oriented approach to mutually recursive definitions. It means a lot of things and nobody really agrees, but here's one very useful OO-style. A class is a definition for a kind of object. (eg. Point) And we have inheritance, so we can re-use code. (eg. coloredPoint) fields and methods Everything is an Object (except base types), builds an inheritance tree. But the examples above aren't recursively defined. Here's a kind of object that is: A File is either a TextFile or an ExecutableFile or a Directory A Directory is a linked list of Files. A TextFile is a linked list of Strings (not really). An ExecutableFile is an array of integers (not really). We want methods like: total number of files, all files containing a particular String, etc. Now what do we do? They should all extend File and override the methods. But there's no such thing as a concrete File that isn't a special kind of File. Let's make the class anyway: abstract class File { abstract public int numberOfFiles(); abstract public FileList filesContainingStr(String str); } Now we can extend this. If we define everything that's abstract, then we don't have to be abstract. class TextFile extends File { private StringList text; public int numberOfFiles() { return 1; } public FileList filesContainingStr(String str) { StringList textLeft = text; while (textLeft != null) { if (textLeft.val.equals(str)) { FileList f = new FileList(); f.val = this; f.rest = null; return f; } } return null; } } class ExecutableFile extends File { private int [] stuff; public int numberOfFiles() { return 1; } public FileList filesContainingStr(String str) { return null; } } class Directory extends File { private FileList contains; public int numberOfFiles() { int answer = 1; // remember a Directory (this) is a File FileList filesLeft = contains; while (filesLeft != null) { answer += filesLeft.val.numberOfFiles(); } return answer; } public FileList filesContainingStr(String str) { FileList answer = null; FileList filesLeft = contains; while (fileLeft != null) { FileList nextList = filesLeft.val.filesContainingStr(String str); if (nextList != null) { answer = nextList.append(answer); } } return answer; } } Would also need constructors, methods for adding, deleting, modifying files, etc. We can add whatever we want, but once we just know something is a File, then we can use all and only those methods defined for Files. So in the data structure, we don't know which kind is there, but we know it can serve our needs. Do this on your homework! Later we could add new kinds of Files without changing any of these classes! * Exceptions (I've been waiting for this one) What should happen when you try to pop an empty stack? * return a default value: which one? every caller _has_ to deal with it. * exit the program: no flexibility! * throw an exception and let anybody who _wants_ to catch it. * documents the problem * allows non-local control The last one is a better appraoch. Do this on your homework too! if (size == 0) throw new StackEmptyException() Object ans = head.val; head = head.next; return ans; It's different than a return -- think in terms of the call stack. It keeps going up until a method catches it. If nobody catches it, then it's like an exit with information. Catching: try { ... blah blah blah... Stack s = new Stack(); FunctionThatUsesTheStack(s); } catch (StackEmptyException e) { ... clean-up code... } // could catch other things too! Nuts and bolts: * You must define your new kind of exception as a class that extends Exception. * Any method that throws an exception must declare this in its header. It throws an exception if it does so explicitly or a method it calls does and it does not catch it. * There are special kinds of exceptions that don't need to be declared if they are thrown. Don't be lazy and use these -- they're for things that could get thrown almost anywhere. Exceptions inherit (they're objects after all) -- this is great for catching kinds of exceptions together You can even throw some other more descriptive exception: try { ... do something with token... } catch (NumberFormatException e) { throw new CalculatorIllegalInputException("Illegal token " + token); } Other things I use them for: * documenting (supposedly) impossible cases. * keeping the window from disappearing (by catching all Exception s in main and calling System.in.read()). Personally, Java got it right. C++ has catch and throw but they interact badly with explicit memory management. C has setjmp, longjmp which don't document anything, are obscure, and have worse memory problems. C programmers really do special value (with bubbling up) or default value and global variable. Invariably programmers forget to check, though!