OCA/OCP Java Note (11): Exceptions
Contents
1. Understanding Exception Types
Java中异常的种类如图1所示。
Error用于指示严重错误,程序不应尝试从错误中恢复。RuntimeException和其子类表示运行时异常,用于指示意外的非致命异常,也被称为unchecked exception。
Runtime (Unchecked) exception是异常的一个种类,并不是指在程序运行期间(run time)发生的异常。所有异常都发生在程序运行期间,与之相对的是编译时(compile time),编译时可能会发生编译错误。
Checked exception包括Exception和其没有继承RuntimeException的子类,对于checked exception,Java要求代码要么处理,要么在函数上声明(handle or declare rule)。
以上内容总结如图2。
eg.
void fall() throws Exception { throw new Exception(); }
2. Using a try Statement
2.1. Adding a finally Block
finally总会执行,但有一种特例:如果在try或catch中调用了System.exit() ,finally就不会执行。
2.2. Catching Various Types of Exceptions
Java会按照在代码中出现的顺序检查各个catch,如果有不可能被执行到的catch,就会给出编译错误。比如在catch子类异常前catch了父类异常。
eg.
class AnimalsOutForAWalk extends RuntimeException { } class ExhibitClosed extends RuntimeException { } class ExhibitClosedForLunch extends ExhibitClosed { } public void visitMonkeys() { try { seeAnimal(); } catch (ExhibitClosed e) { System.out.print("not today"); } catch (ExhibitClosedForLunch e) { // DOES NOT COMPILE System.out.print("try back later"); } }
2.3. Throwing a Second Exception
eg.
30: public String exceptions() { 31: String result = ""; 32: String v = null; 33: try { 34: try { 35: result += "before"; 36: v.length(); 37: result += "after"; 38: } catch (NullPointerException e) { 39: result += "catch"; 40: throw new RuntimeException(); 41: } finally { 42: result += "finally"; 43: throw new Exception(); 44: } 45: } catch (Exception e) { 46: result += "done"; 47: } 48: return result; 49: }
第36行抛出NullPointerException,37行被跳过;40行抛出RuntimeException,41行后的finally仍会被执行。最后result 为:
before catch finally done
3. Recognizing Common Exception Types
3.1. Runtime Exceptions
Runtime exception不强制要求被处理或声明,可由JVM或程序员抛出。
3.1.1. ArithmeticException
尝试进行除以0的运算时由JVM抛出。
eg.
int answer = 11 / 0;
抛出异常:
Exception in thread "main" java.lang.ArithmeticException: / by zero
3.1.2. ArrayIndexOutOfBoundsException
使用非法索引访问数组时由JVM抛出。
eg.
int[] countsOfMoose = new int[3]; System.out.println(countsOfMoose[-1]);
抛出异常:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
3.1.3. ClassCastException
转换为子类,但对象并不是子类的实例时,由JVM抛出。编译器会首先对转换进行检查,更复杂的转换错误会在运行时抛出异常。
eg. Integer不是String的子类,下面的代码会给出编译错误:
String type = "moose"; Integer number = (Integer) type; // DOES NOT COMPILE
下面的代码会抛出异常:
String type = "moose"; Object obj = type; Integer number = (Integer) obj;
抛出异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
3.1.4. IllegalArgumentException
指示传入的参数不合法或不合适,由程序员抛出。
eg. 对于如下的代码:
public static void setNumberEggs(int numberEggs) { if (numberEggs < 0) throw new IllegalArgumentException("# eggs must not be negative"); this.numberEggs = numberEggs; }
调用setNumberEggs(-2) ,会抛出:
Exception in thread "main" java.lang.IllegalArgumentException: # eggs must not be negative
3.1.5. NullPointerException
访问对象时发现null引用,由JVM抛出。
eg.
String name; public void printLength() throws NullPointerException { System.out.println(name.length()); }
抛出异常:
Exception in thread "main" java.lang.NullPointerException
3.1.6. NumberFormatException
将字符转换为数值类型,字符格式不正确时,由程序员抛出。
eg.
Integer.parseInt("abc");
抛出异常:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
3.2. Checked Exceptions
Checked exception都可以由JVM或程序员抛出。
3.2.1. FileNotFoundException
尝试引用不存在的文件时抛出,是IOException的子类。
3.2.2. IOException
读写文件出现问题时抛出。
3.3. Errors
错误继承Error类,由JVM抛出,不应被处理或声明。
3.3.1. ExceptionInInitializerError
静态初始化器抛出异常且未处理时,由JVM抛出。
eg.
static { int[] countsOfMoose = new int[3]; int num = countsOfMoose[-1]; } public static void main(String[] args) { }
抛出关于两个异常信息:
Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.ArrayIndexOutOfBoundsException: -1
3.3.2. StackOverflowError
方法调用自身次数过多(无限递归)时,由JVM抛出。
eg.
public static void doNotCodeThis(int num) { doNotCodeThis(1); }
抛出错误:
Exception in thread "main" java.lang.StackOverflowError
3.3.3. NoClassDefFoundError
类的代码在编译时可用,而在运行时不可用时,由JVM抛出。
4. Calling Methods That Throw Exceptions
Checked exception必须被处理或声明,否则会出现编译错误。
eg.
class NoMoreCarrotsException extends Exception {} public class Bunny { public static void main(String[] args) { eatCarrot(); // DOES NOT COMPILE } private static void eatCarrot() throws NoMoreCarrotsException { } }
上面的代码在main() 中eatCarrot() 的异常没有被处理,导致编译错误。以下两种main() 可以正常编译:
public static void main(String[] args) throws NoMoreCarrotsException {// declare exception eatCarrot(); } public static void main(String[] args) { try { eatCarrot(); } catch (NoMoreCarrotsException e ) {// handle exception System.out.print("sad rabbit"); } }
编译器会检查无法运行到的代码(unreachable code),声明但不抛出异常是合法的,但尝试catch没有抛出的异常会导致编译错误。
eg.
public void bad() { try { eatCarrot(); } catch (NoMoreCarrotsException e ) { // DOES NOT COMPILE System.out.print("sad rabbit"); } } public void good() throws NoMoreCarrotsException { eatCarrot(); } private static void eatCarrot() { }
4.1. Subclasses
子类尝试重写父类方法或者实现接口方法时,不允许在函数签名上添加新的checked exception。
eg.
class CanNotHopException extends Exception { } class Hopper { public void hop() { } } class Bunny extends Hopper { public void hop() throws CanNotHopException { } // DOES NOT COMPILE }
子类可以声明更少的异常
eg.
class Hopper { public void hop() throws CanNotHopException { } } class Bunny extends Hopper { public void hop() { } }
子类也可以声明抛出父类或者接口方法所抛出异常的子类。
eg.
class Hopper { public void hop() throws Exception { } } class Bunny extends Hopper { public void hop() throws CanNotHopException { } }
上面的规则只适用于checked exception。子类可以声明新的runtime exception,因为对runtime exception的声明是冗余的,方法可以任意抛出任何runtime exception而不需要进行声明。
eg.
class Hopper { public void hop() { } } class Bunny extends Hopper { public void hop() throws IllegalStateException { } }
4.2. Printing an Exception
有三种方式可以打印出异常:让Java使用默认方式打印,打印异常消息,或者打印栈跟踪信息。
eg. 下面的例子使用这三种方式打印异常:
5: public static void main(String[] args) { 6: try { 7: hop(); 8: } catch (Exception e) { 9: System.out.println(e); 10: System.out.println(e.getMessage()); 11: e.printStackTrace(); 12: } 13:} 14:private static void hop() { 15: throw new RuntimeException("cannot hop"); 16:}
输出为:
java.lang.RuntimeException: cannot hop cannot hop java.lang.RuntimeException: cannot hop at trycatch.Handling.hop(Handling.java:15) at trycatch.Handling.main(Handling.java:7)