CS 1302 – Ch 12 – Exception Handling These notes will cover Sections 12.1-12.7 A good reference on exceptions: http://mindprod.com/jgloss/exception.html Sections 12.1-12.2 – Exception Handling Overview 1. What is a run-time error? When you code tries to do something illegal, or just can’t do a run-time error occurs, an exception is thrown, and your code stops. 2. Example 1: ArithmeticException – Divide by zero and others. Code int x=20, y=0; int z=x/y; Output Exception in thread "main" java.lang.ArithmeticException: / by zero 3. Example 2: ArrayIndexOutOfBoundsException – Attempting to access an array element that doesn’t exist Code int[] vals = new int[3]; vals[50]=100; Output Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 50 4. Example 3: IndexOutOfBoundsException – An index of some sort is out of range. Code ArrayList<Integer> vals = new ArrayList<>(); int x = vals.get(50); Output Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 50, Size: 0 1 5. Example 4: StringIndexOutOfBoundsException – An index in a string is out of range. Code String dog = "Snoopy"; Character c = dog.charAt(33); Output Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 33 6. Example 5: NullPointerException – Attempting to use null when an object was excepted. Code A a = null; int x = 1; if( x > 5 ) a = new A(); a.m(); class A { public void m() {} } Output Exception in thread "main" java.lang.NullPointerException 7. Example 6: ClassCastException – Trying to cast an object to something it isn’t Code Dog dog = new Dog(); WolfDog wolfDog = (WolfDog)dog; class Dog {} class WolfDog extends Dog {} Output Exception in thread "main" java.lang.ClassCastException: errors1.Dog cannot be cast to errors1.WolfDog 2 8. The exception hierarchy is shown below: 9. Handling Errors – For robust software systems we must try to handle run-time errors so that the program does not stop unexpectedly. In general, there are two techniques: 1. Anticipate and trap sources of error before they result in a run-time error. 2. Detecting and handling run-time errors after they occur. 3 10. Anticipating Errors – Some errors we may be able to anticipate. For instance, suppose we have a method to divide two numbers. If the denominator is 0, then a run-time error will result and the program will halt. Thus, it is easy to trap that source of error with an if statement: class A2 { int z=0; public void divide1( int x, int y ) { if( y != 0 ) z = x/y; else System.out.println( "y can't be 0." ); } } However, this solution is poor because: (a) displaying a message to the console is practically useless in a gui application, (b) the calling method is not informed of the error. It has no way of knowing weather the calculation was successful or not. Similarly, the method below is not very good either. A better solution would be to inform the calling method with an indicator of whether the method was successful: public boolean divide3( int x, int y ) { if( y != 0 ) { z = x/y; return true; } else return false; } 11. It is hard to anticipate every single situation that could cause a run-time error. For example, the method below could fail because: (a) an array index reference that is too big (or small), (b) the value of the denominator could be zero, (c) the vals array might not have been created. public void divide4( int x, int y ) { z = vals[x]/vals[y]; } 4 12. Detecting Run-Time Errors – Hopefully it is clear, that even if we had an abundance of time, it would be nearly impossible to anticipate every possibly source of error. Thus, run-time errors are going to occur. In Java, we can use a try/catch block to detect run-time errors immediately after they occur and allow us to provide code to keep the code running, inform the user, send an error report, correct the error, etc. try { // code that might fail } catch( Exception e ) { // code to recover from failure } The idea is to surround a method call that may fail (in general, any piece of code that may fail) with a try/catch block. Thus, the JVM tries to execute the method and if it fails it catches the exception that is thrown (running the code in that block), instead of terminating the program. 13. Example – the divide4 method below can fail in multiple ways as stated above. For the hard-coded main, we see that the method will fail due to an index out of bounds. 5 The path of execution for a successful call to divide4 is shown below. 14. Example – Consider the (pseudo) code below and assume that statement2 is subject to run-time failure. Code statement1; try { statement2; statement3; } catch( Exception e ) { statement4; } statement5; Statements that execute when: a. statement2 fails: 1, 4, 5 b. statement2 succeeds: 1, 2, 3, 5 6 15. RuntimeException – The java.lang package defines the Exception class of which RuntimeException is a subclass. Two of its important methods are: toString and printStackTrace. a. toString – The toString method gives the actual class of the Exception and sometimes some additional information. For example: java.lang.ArrayIndexOutOfBoundsException: 4 where the “4” is the index that was out of bounds. b. printStackTrace –The printStackTrace is much more useful because it displays the stack frames that exist when the error occurs. Thus, in the example below we can see that the error occurred on line 92 which was called from line 27. java.lang.ArrayIndexOutOfBoundsException: 4 at ch14_exceptions.A2.divide4(FirstTryCatch.java:92) at ch14_exceptions.FirstTryCatch.main(FirstTryCatch.java:27) 16. Creating and throw an Exception – Another approach to error detection is to create and throw our own exceptions. In the example below, the method detects an error, creates, and throws an exception: class A { public void m1( int x, int y ) { if( y == 0 ) throw new ArithmeticException("y can't be 0."); int z = x/y; } } Which can be caught with this code: try { A a = new A(); a.m1(4,0); } catch( ArithmenticException e ) { System.out.println( e ); } Output: java.lang.ArithmeticException: y can't be 0. 7 Sections 12.3 – Exception Types 1. Exception Hierarchy – The Exception hierarchy defined in the API is: a. The Exception class describes errors caused by your program and external circumstances. These errors can be caught and handled by your program. b. RuntimeException is caused by programming errors, such as bad casting, accessing an out-ofbounds array, and numeric errors. c. System errors are thrown by JVM and represented in the Error class. The Error class describes internal system errors. Such errors rarely occur. If one does, there is little you can do beyond notifying the user and trying to terminate the program gracefully. 2. Unchecked Exceptions – RuntimeException, Error and their subclasses are known as unchecked exceptions. This means that these types of errors can and will occur, but you do not have to catch them, e.g try/catch is not required. All other exceptions are known as checked exceptions, meaning that the compiler forces the programmer to check and deal with the exceptions. For instance, when you attempt to read from a text file, it is possible to throw an IOException which must be caught. 8 Sections 12.4 – More on Exception Handling 1. Declaring an Exception – A method that throws an exception must either catch it, or, as in the example below, declare the exception which in turn requires the calling method to catch it. declare exception method1() { method2() throws Exception { try { invoke method2; } catch (Exception ex) { Process exception; } catch exception if (an error occurs) { throw new Exception(); } throw exception } } 2. Declaring a Checked Exception – Every method must state the types of checked exceptions it might throw. This is known as declaring exceptions. public void myMethod() throws IOException public void myMethod() throws IOException, OtherException 3. Catching Multiple Exceptions – In general, you can catch multiple types of exceptions by supplying multiple catch blocks. However, only one catch block will activate, the first one that matches (we’ll see more on this later). However, a catch block for a subclass Exception must occur before a catch block for a superclass Exception. Thus, exceptions are caught from most specific to most general. try { statements; // Statements that may throw exceptions } catch (Exception1 exVar1) { handler for exception1; } catch (Exception2 exVar2) { handler for exception2; } ... catch (ExceptionN exVar3) { handler for exceptionN; } 9 4. Example – Consider the divide method below which can fail in multiple ways. class A3 { int[] vals; public A3(int[] vals) { this.vals = vals; } public int divide( int x, int y ) { if( x==3 ) throw new Exception("General Exception"); return vals[x]/vals[y]; } } Next, consider this main. As we see, the first catch block will be activated. public static void main(String[] args) { A3 a = new A3( new int[] {2,4,0,6} ); try { (a) a.divide(1,2); (b) a.divide(4,2); (c) a.divide(3,1); } catch ( IndexOutOfBoundsException e ) { System.out.println( "Caught IndexOutOfBoundsException: " + e ); } catch ( ArithmeticException e ) { System.out.println( "Caught ArithmeticException: " + e ); } catch ( RuntimeException e ) { System.out.println( "Caught RuntimeException: " + e ); } catch ( Exception e ) { System.out.println( "Caught Exception: " + e ); } } Output: (a) a.divide(1,2); - Caught ArithmeticException (b) a.divide(4,2); - Caught IndexOutOfBoundsException (c) a.divide(3,1); - Caught Exception 5. Example – Modify the try block above with: A3 a = new A3( null ); // Null pointer exception try { a.divide(1,2); } In this case a RuntimeException is caught, but the actual exception is NullPointerException. 10 Sections 12.5 – The finally Clause 1. try/catch/finally – You can add a finally block to a try/catch to make it try/catch/finally. The finally block of code is always executed. try{ Statements } catch (Exception e { statements } finally { statements } 2. Example 11 Output: ÏÏ1 ÏÏ2 ÏÏ4 ÏÏ5 ÏÏ ÏÏ1 ÏÏ3 ÏÏ4 ÏÏ5 3. Notice that “4” and “5” are printed no matter what the case. So, how is finally adding anything? We’ll see in an example in Section 12.7 where we rethrow an excetion. Sections 12.6 – When to Use Exceptions 1. When to throw an Exception: Suppose an exception can occur in a method you have written. If you want the exception to be processed by its caller, you should create an exception object and throw it. If you can handle the exception in the method where it occurs, there is no need to throw it. When should you use the try-catch block in the code? You should use it to deal with unexpected error conditions. Do not use it to deal with simple, expected situations. 2. Example: This code: try { System.out.println(refVar.toString()); } catch (NullPointerException ex) { System.out.println("refVar is null"); } Is better replaced with this code: if (refVar != null) System.out.println(refVar.toString()); else System.out.println("refVar is null"); 12 Sections 12.7 – Rethrowing Exceptions 1. Example – Modify the example from Section 12.5 to re-throw the exception once it is caught in the method. Then, modify main to use a try/catch. In this case a different path is followed when an error occurs, “5” is not printed. If the throw was replaced by a return, the flow would be the same. Output: ÏÏ1 3 4 6 13 2. Example – Illustrates a return statement, and finally. public class RethrowReturn { public static void main(String[] args) { try { A a = new A(); int z = a.divide(2, 3); // // // prints: 1,3,4 int z = a.divide(3, 2); // prints: 5,2,3,4 int z = a.divide(20, 2); // prints: 3 if( z > 4 ) return; System.out.println( "1" ); } catch ( Exception e ) { System.out.println( "2" ); } Finally { System.out.println( "3" ); } System.out.println( "4" ); } } class A { public int divide( int x, int y ) throws Exception { if( x==3 ) { System.out.println( "5" ); throw new Exception("3's a funny number"); } return x/y; } } 14 3. Example – These are two methods from a real system. The first method is called to open a connection to a server and establish input and output communication (socket) channels. final public void openConnection() throws IOException { // Do not do anything if the connection is already open if(isConnected()) return; //Create the sockets and the data streams try { clientSocket= new Socket(host, port); output = new ObjectOutputStream(clientSocket.getOutputStream()); input = new ObjectInputStream(clientSocket.getInputStream()); } catch (IOException ex) { // All three of the above must be closed when there is a failure // to create any of them try { closeAll(); } catch (Exception exc) { } throw ex; // Rethrow the exception. } clientReader = new Thread(this); //Create the data reader thread readyToStop = false; clientReader.start(); //Start the thread } private void closeAll() throws IOException { try { //Close the socket if (clientSocket != null) clientSocket.close(); //Close the output stream if (output != null) output.close(); //Close the input stream if (input != null) input.close(); } catch (Exception exc) { throw exc; finally { // Set the streams and the sockets to NULL no matter what // Doing so allows, but does not require, any finalizers // of these objects to reclaim system resources if and // when they are garbage collected. output = null; input = null; clientSocket = null; } } 15 4. Example – In the examples below, suppose A, B, C, D, E are blocks of code and that A might throw an exception. Code try{ A B Scenario A doesn’t throw exception A throws exception A & C throw exceptions Blocks Executed A, B, D, E A, C, D, E A, C, D Scenario A doesn’t throw exception A throws exception Blocks Executed A, B, D, E A, C, D } catch(Exception e) } C } Finally{ D } E Code try{ A B } catch(Exception e) } C throw e // or return } Finally{ D } E 16