Tutorial 10 Topics Exception o Types of Exception o Catch an exception o Catch multiple exceptions o Create your own exception o Throw an exception Stream o Standard input, output, and error o Input and output streams o Example Binary Searching o Linear search o Binary search o Binary tree Tutorial Problems Design Problem Exception The term exception is shorthand for "exceptional event" which occurs during the execution of a program that disrupts the normal flow of instructions. When JVM encounters an exception, it will stop processing the code it was working on create a specific kind of exception object execute the routine that handles such exception Types of Exceptions There are two types of Java exceptions checked and unchecked. Checked exceptions occur because something was wrong in the environment your code operates in. For example, if your code tries to read from a file that doesn’t exist, the program will throw an IOException. A checked exception is something beyond your control - it may happen even though your code is perfectly correct. Therefore, in your code, you must prepare to handle checked exceptions - you can either catch it or throws it. Unchecked exceptions usually occur because you made a programming error. In this case the best thing you can do is to fix the program. For this reason, you are not supposed to handle them in your code (you should prevent them in the first place). For example, if your code tries to read the 10th element of an array of size 9, the program will throw an ArrayIndexOutOfBoundException. Catch An Exception If you think certain parts of your code might generate an exception, enclose that code inside a try…catch block. Here is the format try { code } catch (XYException e) { handler of XYException } finally { always execute} If any of the code inside the try block creates an exception, the program will skip the remaining codes in the try block and execute the handler code inside the catch block. If no exception happens, the program will finish all the codes in the try block and skip the catch block. Regardless what happens inside the try block, the codes inside the finally block will always be executed. What should you do if you catch an exception? The simplest thing is to print out what it is. There are three ways you could do it getMessage() return a string that describes the exception that occurred toString() return a string made up of the specific exception class name and the error message printStackTrace() prints the sequence of method calls that led to the exception on the standard error stream Here is an example public class T10Exception { int addInputs(String[] args) { int sum = 0; try { for (int i=0; i<args.length; i++) sum += Integer.parseInt(args[i]); } catch (NumberFormatException e) { System.out.println("\nOutput of getMessage() " + e.getMessage()); System.out.println("Output of toString() " + e.toString()); System.out.println("Output of printStackTrace() " + e.printStackTrace(); } return sum; } public static void main(String[] args) { T10Exception self = new T10Exception(); String[] test = {"1", "2", "X"}; self.addInputs(test); } } Here are the inputs and outputs Output of getMessage(): X Output of toString(): java.lang.NumberFormatException: X Output of printStackTrace(): java.lang.NumberFormatException: X at java.lang.Integer.parseInt(Integer.java:414) at java.lang.Integer.parseInt(Integer.java:463) at T10Exception.addInputs(T10Exception.java:6) at T10Exception.main(T10Exception.java:19) In the above example, when user inputs a non-integer value, the method parseInt throws an NumberFormatException which is caught by the catch clause and the program prints out the content of the exception in different formats.Note that printStackTrace() does not return a string. It will directly print out the message to stderr. As covered in lecture, exceptions can inherit from each other. For instance FileNotFoundException extends IOException so a catch (IOException e ) will also catch FileNotFoundException. If you are not sure what exception the code segment will generate, just catch (Exception e), which will catch any exception. Catch Multiple Exceptions You can catch multiple exception types in one try…catch block – just use a separate catch clause for each type as in the following example try { code } catch (IOException e1) { e1.printStackTrace(); } catch (NumberFormatException e2) { e2.printStackTrace(); } Create Your Own Exceptions As with any other Java class, you can extend existing exception classes and create your own exception type. Here is an example Class MyException extends NumberFormatException { public MyException(String msg) { super("Invalid format " + msg); } } Throw an Exception In the above example, we caught the NumberFormatException and handled it on the spot. There might be times when this behavior is not desirable. For example, you are writing a method for other classes to use which could cause an exception. You might want to let the user of the class decide how to handle the exception. In this case, you need to "throw" the exception – passing the handling responsibility to the caller. Here is how to do it. Decide what exception your method will cause Declare the "throws" clause in the method header Handle the exception in the caller (try/catch) Here is the modification of the above example public class T10Exception { int addInputs(String[] args) throws NumberFormatException { int sum = 0; for (int i=0; i<args.length; i++) sum += Integer.parseInt(args[i]); return sum; } public static void main(String[] args) { T10Exception self = new T10Exception(); String[] test = {"1", "2", "X"}; try { self.addInputs(test); } catch (NumberFormatException e) { System.out.println("\nOutput of getMessage() " + e.getMessage()); System.out.println("Output of toString() " + e.toString()); System.out.println("Output of printStackTrace() "); e.printStackTrace(); } } } In this case, the method addInputs decides not to handle the exception. Instead, it throws it to the caller. Therefore, the main method have to catch it and handle it. The output will be the same. Note that a method can declare unchecked exceptions that it throws but it MUST declare checked exceptions or the compiler will complain. NumberFormatException is a good example of an unchecked exception that we DO want to check for. IOException or FileNotFoundException is a good example of a checked exception that you must declare. Streams So far, our programs have been receiving user inputs only from the keyboard, e.g. getText() from JTextField. In this section, we will show you how to input data from and output data to other sources, such as a file or network connection, using stream. Standard Input, Output, and error In fact, you have been using streams to send output to the screen since the beginning of the class System.out.println(String) prints a string to the standard output stream which usually (but doesn't have to) means the "screen". Another common output stream object is the System.err object. The System.err object allows a program to output error messages. Here again, by default this output is usually directed to the screen. System.out and System.err, as well as System.in (which we didn't use much) are created automatically when a Java program executes. These objects would be sufficient if you only wanted to write to the screen or read from the keyboard. Streams The java.io library allows you to input from and output to other data sources, such as disks, inter-process pipelines, or network connections. This is accomplished using stream. Java provides four types of streams InputStream is an object from with we read a sequence of binary data Outputstream is an object to which we write a sequence of binary data Reader is an object from which we read a sequence of text Writer is an object to which we write a sequence of text Each stream has a number of subclasses each handle a particular type of data source. For example InputStream o FileInputStream o ObjectInputStream o PipedInputStream OutStream o FileOutputStream o ObejctOutputStream o PipedOutputStream Once you connect an InputStream to a data source, you can use its read() function to read data from that source. However, the read() function is pretty limited – it can only read byte arrays. Most of the time, you need to add a "filter" to translate bytes into more useful data types. Example of filter input streams include DataInputStream, BufferedInputStream, etc. In summary, to read data from a data source, you need to follow the following procedures 1. Identify the data source (what is it? what type of data does it contain? etc.) 2. Connect an appropriate input stream to that data source (e.g. FileInputStream or ObjectInputStream) 3. Attach a filter stream to that input stream (e.g. DataInputStream) 4. Read data using methods provided by the filter input stream (e.g. readInt()) The same is true for OutputStreams. Next, we will walk you through an input/output example which updates employee records based on the number of hours one has worked for that month. Here are the requirements Example There are two files. The first file, Employee_May.dat, contains 5 employee records with the following format Name, SSN, hourly rate, salary to date Here is the content of the file Wen Xiao, 555-12-3456, 65, 20000 Anil Gupta, 555-22-2222, 70, 30000 Pei-Lei Fan, 555-33-4444, 60, 150000 Katie Chase, 555-44-5555, 80, 40000 The second file, Hours.dat, contains 5 integers, which are the number of hours each employee has worked for that month. The integers have the same sequence as that of the employee records. Here is the content of the file 50 60 40 50 70 Our program reads the number of hours worked from Hours.dat, calculates the monthly salary for that employee, updates her salary to date, and print the new data to a file called Employee_June.dat Note that for illustration purposes, we break down the code into relatively independent blocks. There are certainly more efficient ways to write the program. Import the Headers import java.io.*; import java.util.*; Read the Data File (Hours.dat) 1. Create a File object representing Hours.dat 2. Connect the file object to an input stream (FileInputStream) 3. Attach a filter stream (DataInputStream) to the input stream 4. Read 5 integers from the data input stream 5. Close the input stream File f = new File("Hours.dat"); FileInputStream fin= new FileInputStream(f); DataInputStream din = new DataInputStream(fin); int[] hours = new int[10]; for (int i=0; i<5; i++) hours[i] = din.readInt(); din.close(); Read the Text File (Employee_May.dat) 6. Connect Employee_May.dat to a FileReader 7. Attach a BufferedReader to the file reader 8. Read 5 strings from the buffered reader 9. Close the buffered reader Here, we introduced a new class called BufferedReader. Briefly buffering is a technique that increases input/output efficiency. Instead of immediately reading from or writing to the disk each time you request such an operation, the stream object will use the buffer if it can. For example, if you were writing a series of 256 integers to a file without buffering, each time you issued an writeInt(int) command, the system would write to the disk. With buffering, the disk would only be written to when the buffer was full. BufferedReader also provides the readLine() function which allows use to read lines, instead of characters. FileReader fr = new FileReader("Employee_May.dat"); BufferedReader in = new BufferedReader(fr); String[] records = new String[5]; for (int j=0; j<5; j++) records[j] = in.readLine(); in.close(); Find the Data The readLine() function return a string that contains all 4 fields of an employee record. We need to find the hourly rate and salary to date. 10. Assign the string to a StringTokenizer 11. Find the 3rd and 4th tokens in the string 12. Calculate the salary for this month and add it to the salary to date In this process, we used a utility class called StirngTokenizer which breaks the string into individual pieces (tokens) based on the delimiter. In our example, the delimiter is ", " StringTokenizer st; String name, ssn; double hourlyRate, salary; for(int k=0; k<5; k++) { st = new StringTokenizer(records[k], ", "); name = st.nextToken(); ssn = st.nextToken(); hourlyRate = Double.parseDouble(st.nextToken()); salary = Double.parseDouble(st.nextToken()); salary += hourlyRate * hours[k]; records[k] = name + ", " + ssn + ", " + hourlyRate + ", " + salary } Note that on the parseDouble() calls, you may want to check for NumberFormatException Output the Data 13. Create a new FileWriter with the file name Employee_June.dat 14. Attach a PrintWriter to the file writer 15. Write the string array to the PrintWriter 16. Close the PrintWriter FileWriter fw = new FileWriter("Employee_June.dat"); PrintWriter out = new PrintWriter(fw); for (int l=0; l<5; l++) out.println(records[l]); out.close(); Binary Searching The objective of searching is to locate a specific record within a series. There are several approaches with different efficiencies. Linear search Linear search is the sequential process of scanning through the records, starting at the first record, until either a match is found or the search is completed unsuccessfully. Linear search is appropriate when the records are in random order or if the data is stored in a linear linked list. This approach may be reasonable if the size of the series is small and the content of the series is highly dynamic (e.g. frequent deletion). Binary search Binary search is a straightforward procedure for locating an element in an already sorted array. It follows the "divide and conquer" strategy we used in the bisection method for root finding. It operates by splitting the data set in half, determining which half the desired element is in and then cutting it in half again, etc. That is, binary search is a "divide and conquer" strategy. Binary search is much more efficient than linear search since it requires less interations. However, before using binary search, we must sort the data first. Search with a binary search tree A binary search tree is a binary tree where the left offspring of any node is "less" than the root node and the right offspring is "larger" than the root node. To search for an item in a binary tree, we do the following: Compare the search key with the root key. If it is equal, then the search is completed. Else determine if it is smaller than the root key. If so, then it must be in the left subtree. If it is larger, then it must be in the right subtree. Therefore at each time, around half of the series is eliminated from consideration. Eventually the procedure finds the key in the tree or comes to a NULL node and concludes the key is not stored in the tree. Tutorial Problems 1. Exception In PS7, we ran into the ArrayStack class whose pop() method throws an exception. public Object pop() throws EmptyStackException { if (isEmpty()) throw new EmptyStackException(); else return stack[top--]; } Is this a checked exception or unchecked exception? Why did we decide to throw the exception rather than handle it within the pop method? Modify your PS7 program to handle the exception Design Problem As mentioned in the tutorial, a stream could be a file or a network connection. In this exercise, write a small program to read the html file from a website. Hint: Use URL class instead of File