Tutorial 8 - Generics and Exceptions
Generic programming refers to writing code that will work for many types of data. The Java collections framework promotes generic programing by applying a unifying philosophy that adds operational functionality, and dynamic growth to object classes. Unification is accomplished through interfaces that each collection object inherits. Various types of objects may be handled in a similar manner within collection framework classes. Functionality such as searches, sorts, insertion and deletion use highly efficient algorithms.
Exception handling is a method of trapping or coping with anticipated errors (system, data entry or calculation) and handling or dealing with them in a graceful manner. The Exception class of objects offers a rich group of subclasses to trap specific errors and recover from them.
Note:Unless another library is indicated the following classes are contained in the java.lang package.
Collections and Mappings
Note: All collection objects are unsynchronized. If multiple thread programming is used, place the collection in a synchronization wrapper.
The collection interfaces are Collection, List, Set, SortedSet and Queue. They are dynamic data structures similar to arrays. Collection is at the top of this hierarchy and includes the core methods add(obj), addAll(coll), clear(), contains(obj), containsAll(coll), equals(obj), hashCode(), isEmpty(), iterator(), remove(obj), removeAll(coll), retainAll(coll), size(), and toArray(). List extends Collection for classes that are a sequence of elements (similar to an array). It adds the methods get(idx), indexOf(), lastIndexOf(), set(idx) and subList(i1,i2) and overrides appropriate methods. Set extends Collection by overriding the add() method in a way that prevents duplicate objects. SortedSet extends Set to keep all contained objects in a ordered manner. It includes the methods comparator(), first(), headSet(obj), last(), subSet(o1,o2) and tailSet(obj). Queue puts objects in a line pending processing.
Concrete collection classes implement the appropriate interfaces and incorporate advanced and optimized data structures for programming ease. The main collection classes include ArrayList, HashSet, LinkedList and TreeSet. ArrayList implements List and is very similar to arrays but is dynamic (ie. its size does not have to be declared at compile time). Some additional methods in ArrayList include lastIndexOf(obj) to return the object's position, get(index) to return an object at a specific position, and toArray() for converting a collection back to a regular array. HashSet implements the Set interface and uses a hash table for efficient storage. LinkedList implements the appropriate data structure which can be traversed and nodes inserted and deleted. TreeSet implements a sorted set using a binary tree for storage, allowing rapid access to list elements.
An example of how specific list objects can be located for access by indexing is:
// Staff is a list object
for (int x=0; x<emp.size(); x++) // by index number
{ if (emp.get(x) instanceof Staff) // emp can be of several types
{ Staff s = (Staff) emp.get(x);} } // cast it to new Staff object
An example of walking through a collection with an iterator is:
Iterator it=c.iterator(); // set up iterator for the collection
while (it.hasNext()) {this.add((String)it.next());} // check, then access
The mapping interfaces are Map and SortedMap. These are similar to 'associative arrays' in other languages. Map defines a table that is a one-to-one relationship between a key and an element. No duplicate keys are allowed. SortedMap maintains order within the mapping.
HashMap and TreeMap are concrete mapping classes. TreeMap orders the mapping key while HashMap does not.
Utilities and Algorithms
The utility interfaces include Comparator, Iterator and ListIterator. Their methods can be customized by overriding. Comparator defines how two objects are compared. It has two methods, compare(obj1,obj2) and equals(obj) Iterator and ListIterator enumerate objects within a list (similar to an array index). Iterator contains the methods next(), hasNext() and remove(). ListIterator has the additional methods of hasPrevious(), previous() and set().
Several algorithms are defined as static methods within the collections interface. They are binarySearch(), binarySort(), copy(), enumeration(), fill(), max(), min(), nCopies(), reverse(), reverseOrder(), shuffle(), singleton(), sort(), sychronizedCollection(), synchronizedList(), synchronizedMap(), synchronizedSet(), synchronizedSortedMap(), synchronizedSortedSet(), unmodifiableCollection(), unmodifiablelist(), unmodifiableMap(), unmodifiableset(), unmodifiableSortedMap() and unmodifiableSortedSet().
Parameterized Types
Note: A ClassCastException error is generated if objects are incompatible such as adding an incompatible object to a collection. Collection objects can be type checked at compile time by using parameterized types to avoid casting errors at runtime.
LinkedList<Integer> myIntList = new LinkedList<Integer>(); myIntList.add(new Integer(0)); Integer x = myIntList.iterator().next();
The type declaration inside the angle bracket ensures checking at compile time. No casting is required! Types specified must be class types. Primitives must use their wrapper objects. Mappings such as HashMap and TreeMap specify objects by using an ordered list (eg. <String,Float>).
Warning: Use wildcard typing <?> at your own peril!!!
Project: Convert Arrays to Lists
Static arrays can be converted easily to dynamic lists. This can be readily appreciated when our WordCount2 example is made into wcPlus with an unlimited number of unique words instead of a precompiled limit. A single list of word objects is used instead of parallel word and count arrays. Iterators are used instead of indexes when walking through the list.
Project: Concordance
Concordance produces an index of words and the line numbers of occurrence within a document. Java collections should be used to add dynamic size (ArrayList) and sorting (TreeMap), making the indexing easier to program. The StringTokenizer class is used to provide the words/locations to be indexed. The report should be in columns for readability. concordance.java (a command line solution) and concord.java (GUI based) use file IO techniques. concord.java is also used as an example of how an application is deployed using Web Start.
Project: Word Checking
Word checking utilities compare the words in a document against a 'dictionary'. If a word is not found, it and its line number or context is displayed. The utility can be modified so that 'found' words are displayed and the dictionary could be a list of required searches or of words that require avoidance.
Begin by reading the dictionary into a sorted list. Then read the document line by line, parsing and checking each as you read the line. Issue any errors immediately. No demo is given.
Exception Handling
Exceptions are objects that describe any error caused by an external resource not being available or an internal processing problem. They are passed to exception handlers written by the programmer to enable graceful recovery. If the handler has not been written, the program will terminate with a display of the Exception class. There are many exception classes such as IOException and NumberFormatException.
Java uses the try - catch - finally syntax to test (ie. try) a section of code and if an exception error occurs in that region, to trap (ie. catch) the error. Any number of catches can be set up for various exception types. The finally keyword can be used to provide a block of code that is performed regardless of whether an exception is signaled or not. The syntax is:
try
{
// tested statement(s);
}
catch (ExceptionName e1)
{
// trap handler statement(s);
}
catch (ExceptionName e2) // any number of catch statements
{
// display exception to screen
System.out.println("Exception: " + e2);
}
finally
{
// always executed block
}
throws Clause and throw Keywords
The throws clause is used to pass a possible exception up the stack (processing chain). The Java compiler is aware of how some methods may cause specific exceptions and it forces you to deal with these immediately. If you choose not to write an exception handler then use the throws xxxException clause on the surrounding method to abdicate responsibility. For example System.in.read() will give a compile error for IOException. Add the throws clause to the surrounding method to pass the error up to the next level (or else write your own catch/try handler).
The throw keyword (note the singular form) is used to force an exception. It can also pass a custom message to your exception handling module. For example:
throw new FileNotFoundException("Could not find beatles.txt");
Effective Exception Class Hierarchy
The exception classes are arranged in a hierarchy. Handlers (or catches) for specific exceptions should always be written prior to the generic handler. For example since FileNotFoundException is a child of IOException, a catch for FileNotFoundException should occur before the one for IOException. The latter handler will catch those exceptions that are missed by individual child handlers. And a generic handler for Exception would cover any missing situation.
FileInputStream fis=null; // declare in outer block
try
{
fis = new FileInputStream (new File (args[0])); // uses cmd line
int ch;
while (ch = fis.read() != -1)
{System.out.print ((char) ch);}
fis.close();
System.out.println("");
}
catch (FileNotFoundException e)
{
System.out.println("File not found!");
try
{
fis.close();
}
catch (IOException ioe) {} // disregard close failure
}
catch (IOException e)
{
try
{
fis.close();
}
catch (IOException ioe) {} // disregard close failure
System.out.println("Unable to read file!");
}