Exceptions
NullTest.java |
1 | public class NullTest { |
2 | void doNothing() {} |
3 | public static void main(String[] args) { |
4 | NullTest n1 = new NullTest(); |
5 | NullTest n2 = null; |
6 | try { |
7 | System.out.println("n1="+n1); |
8 | System.out.println("n2="+n2); |
9 | n1.doNothing(); |
10 | n2.doNothing(); |
11 | } catch(NullPointerException npe) { |
12 | System.out.println("Don't worry about that null!"); |
13 | } |
14 | } |
15 | } |
$ javac NullTest.java
| $ java NullTest
n1=NullTest@164f1d0d
n2=null
Don't worry about that null!
|
You learned about the value of null and about NullPointerException in the lesson on objects. We learned
how to handle that error by checking to see if a pointer was null. While that is probably the best
way of handling a NullPointerException, it is not the only way. The try {} catch {} block provides
an alternative.
Notice what it looks like when we print out the NullTest class. NullTest inherits the toString() method
from class Object. It is not very pretty or informative, but you can easily see the difference between
an object allocated with "new" and a null.
What if you want to see that familiar stack trace with the line numbers, etc.? Well, you still can
using the printStackTrace() method.
NullTest2.java |
1 | public class NullTest2 { |
2 | void doNothing() {} |
3 | public static void main(String[] args) { |
4 | NullTest n1 = new NullTest(); |
5 | NullTest n2 = null; |
6 | try { |
7 | System.out.println("n1="+n1); |
8 | System.out.println("n2="+n2); |
9 | n1.doNothing(); |
10 | n2.doNothing(); |
11 | } catch(NullPointerException npe) { |
12 | System.out.println("Don't worry about that null!"); |
13 | System.out.println("Stack trace below:"); |
14 | npe.printStackTrace(); |
15 | System.out.println("Stack trace above."); |
16 | } |
17 | } |
18 | } |
$ javac NullTest2.java
| $ java NullTest2
n1=NullTest@23fc4bec
n2=null
Don't worry about that null!
Stack trace below:
java.lang.NullPointerException
at NullTest2.main(NullTest2.java:10)
Stack trace above.
|
Not only can you catch exceptions, but you can throw them as well. You can use keyword
throw to do it.
Try.java |
1 | public class Try { |
2 | public static void main(String[] args) { |
3 | double num = 3, denom = 0; |
4 | try { |
5 | System.out.println("before exception"); |
6 | if(denom == 0) throw new Exception("Denominator is zero"); |
7 | System.out.println("ratio is "+(num/denom)); |
8 | } catch(Exception e) { |
9 | System.out.println("Stack trace below:"); |
10 | e.printStackTrace(); |
11 | } |
12 | } |
13 | } |
$ javac Try.java
| $ java Try
before exception
Stack trace below:
java.lang.Exception: Denominator is zero
at Try.main(Try.java:6)
|
Notice that we explicitly caught the exception. What happens if you try to use
throw without catch?
Try2.java |
1 | public class Try2 { |
2 | public static void main(String[] args) { |
3 | double num = 3, denom = 0; |
4 | System.out.println("before exception"); |
5 | if(denom == 0) throw new Exception("Denominator is zero"); |
6 | System.out.println("ratio is "+(num/denom)); |
7 | } |
8 | } |
$ javac Try2.java
Try2.java:5: unreported exception java.lang.Exception; must be caught or declared to be thrown
if(denom == 0) throw new Exception("Denominator is zero");
^
1 error
|
The answer is that the code won't compile because the compiler checks to see that
the exception is handled properly. Possibly, you don't want to handle
the exception in the method you wrote, so you can pass the responsibility on by
supplying a throws clause to the method.
Try3.java |
1 | public class Try3 { |
2 | public static void main(String[] args) throws Exception { |
3 | double num = 3, denom = 0; |
4 | System.out.println("before exception"); |
5 | if(denom == 0) throw new Exception("Denominator is zero"); |
6 | System.out.println("ratio is "+(num/denom)); |
7 | } |
8 | } |
$ javac Try3.java
| $ java Try3
before exception
Exception in thread "main" java.lang.Exception: Denominator is zero
at Try3.main(Try3.java:5)
|
In the above we added a "throws" clause to the function definition and the compiler is happy.
Another option is to use a different type of exception.
Try4.java |
1 | public class Try4 { |
2 | public static void main(String[] args) { |
3 | double num = 3, denom = 0; |
4 | System.out.println("before exception"); |
5 | if(denom == 0) throw new RuntimeException("Denominator is zero"); |
6 | System.out.println("ratio is "+(num/denom)); |
7 | if(true) throw new Error(); |
8 | } |
9 | } |
$ javac Try4.java
| $ java Try4
before exception
Exception in thread "main" java.lang.RuntimeException: Denominator is zero
at Try4.main(Try4.java:5)
|
There are two basic types of exceptions that are not checked by the compiler (called
unchecked exceptions). They are RuntimeException and Error. Conceptually, Error's are
things you probably should not try and catch, whereas RuntimeExceptions are fair game.
While most of the examples of exceptions above take a String as argument, it is not required. The
Error we generate above takes an empty constructor.
BadDenom.java |
1 | public class BadDenom extends RuntimeException { |
2 | public static void main(String[] args) { |
3 | double num = 3, denom = 0; |
4 | System.out.println("before exception"); |
5 | if(denom == 0) throw new BadDenom(); |
6 | System.out.println("ratio is "+(num/denom)); |
7 | } |
8 | } |
$ javac BadDenom.java
| $ java BadDenom
before exception
Exception in thread "main" BadDenom
at BadDenom.main(BadDenom.java:5)
|
You should feel free to create your own types of exceptions, subclassing either Exception, RuntimeException,
or Error. In this case, we chose to subclass RuntimeException.
BadDenom2.java |
1 | public class BadDenom2 extends Exception { |
2 | public static void main(String[] args) { |
3 | try { |
4 | double num = 3, denom = 0; |
5 | System.out.println("before exception"); |
6 | if(denom == 0) throw new BadDenom2(); |
7 | System.out.println("ratio is "+(num/denom)); |
8 | } catch(BadDenom2 bd) { |
9 | throw new RuntimeException(bd); |
10 | } finally { |
11 | System.out.println("goodbye!"); |
12 | } |
13 | } |
14 | } |
$ javac BadDenom2.java
| $ java BadDenom2
before exception
goodbye!
Exception in thread "main" java.lang.RuntimeException: BadDenom2
at BadDenom2.main(BadDenom2.java:9)
Caused by: BadDenom2
at BadDenom2.main(BadDenom2.java:6)
|
In this example we subclassed Exception instead of RuntimeException. Naturally, since we did this, we had
to provide a try / catch block. Here we have added the finally block. You can put things in a finally block
if you want them to exit after the try block, and they will get executed regardless of what exceptions are
thrown.
We also illustrate the concept of wrapping and re-throwing exceptions. On line 9 we create a new
RuntimeException, giving it our BadDenom2 exception as an argument. When the stack trace prints, we see
both exceptions in the output.
BadDenom3.java |
1 | public class BadDenom3 extends Exception { |
2 | public static void main(String[] args) { |
3 | try { |
4 | double num = 3, denom = 0; |
5 | System.out.println("before exception"); |
6 | if(denom == 0) throw new BadDenom2(); |
7 | System.out.println("ratio is "+(num/denom)); |
8 | } catch(BadDenom bd) { |
9 | System.out.println("caught bd"); |
10 | } catch(BadDenom2 bd2) { |
11 | System.out.println("caught bd2"); |
12 | } finally { |
13 | System.out.println("goodbye!"); |
14 | } |
15 | } |
16 | } |
$ javac BadDenom3.java
| $ java BadDenom3
before exception
caught bd2
goodbye!
|
Here we demonstrate that it is possible to have multiple catch clauses for a single try.
BadDenom4.java |
1 | public class BadDenom4 extends Exception { |
2 | public static void main(String[] args) { |
3 | try { |
4 | double num = 3, denom = 0; |
5 | System.out.println("before exception"); |
6 | if(denom == 0) throw new BadDenom(); |
7 | System.out.println("ratio is "+(num/denom)); |
8 | } finally { |
9 | System.out.println("goodbye!"); |
10 | } |
11 | } |
12 | } |
$ javac BadDenom4.java
| $ java BadDenom4
before exception
goodbye!
Exception in thread "main" BadDenom
at BadDenom4.main(BadDenom4.java:6)
|
And here we show you that you can just have try / finally.
|