在没有异常机制的时候我们对错误处理一般是这样实现的:通过函数的返回值来判断是否发生了异常(这个返回值通常是已经约定好了的),调用该函数的程序负责检查并且分析返回值。虽然可以解决异常问题,但是这样做存在以下缺陷:
为了解决上述存在的问题,引入了异常机制。它在程序发生异常时,强制终止程序运行,记录异常信息并将这些信息反馈给我们,由我们来确定是否处理异常。它能使我们摆脱各种返回值的约定以及实现逻辑代码与错误处理代码的分离。
从图中可以看出Throwable是java所有Error和Exception的超类。
Error和Exception的区别
CheckedException和RuntimeException的区别
java 异常相关的关键字有5个:try, catch, finally, throw, throws。
语法形式
try {
// 可能会发生异常的程序代码
} catch (ExceptionType1 id1) {
// 捕获并处理try抛出的异常类型Type1
} catch (ExceptionType2 id2) {
// 捕获并处理try抛出的异常类型Type2
} finally {
// 无论是否发生异常,都将执行的语句块
}
try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
catch 块:用于处理try捕获到的异常。
finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。
public class Main {
public static void main(String[] args) {
try {
int i = 1 / 0;
return;
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} finally {
System.out.println("finally");
}
}
}
运行结果:
finally
java.lang.ArithmeticException: / by zero
at com.tongfang.learn.Main.main(Main.java:36)
在以下特殊情况下,finally块不会被执行:
public class Main {
public static void main(String[] args) {
try {
int i = 1 / 0;
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} finally {
throw new NullPointerException();
System.out.println("finally"); //编译错误,语句不可达
}
}
}
public class Main {
public static void main(String[] args) {
try {
System.exit(0);
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} finally {
System.out.println("finally");
}
}
}
运行结果:
Process finished with exit code 0
throw:对于异常可以捕获,也可以抛出,该关键字用于抛出异常,让调用者来捕获。
throws:声明一个方法可能抛出的异常。
catch匹配
异常的捕获顺序为按照程序中定义的异常顺序依次匹配,当匹配到对应的异常则进入异常处理程序。
我们看下面的程序:
public class Main {
public static void main(String[] args) {
try {
int i = 1 / 0;
} catch (Exception e){
e.printStackTrace();
} catch (ArithmeticException e) { //编译错误
e.printStackTrace();
} finally {
System.out.println("finally");
}
}
}
按照catch匹配原则,本例中ArithmeticException是Exception的子类,所有的Exception 将会被第一个catch捕获,后面的catch将永远不可达,所以会编译报错。因此catch 代码应按照先捕获小异常,再捕获大异常的原则实现。
异常对继承的影响
方法重写中声明异常原则:
class A{
public void method() {
}
}
class B extends A{
public void method() throws FileNotFoundException {
//编译错误,父类中该方法没有声明抛出异常。
}
}
class A{
public void method() throws IOException{
}
}
class B extends A{
public void method() throws Exception {
//编译错误,抛出的Exception是IOException的父类
}
}
class A{
public void method() throws IOException{
}
}
class B extends A{
public void method() throws FileNotFoundException {
//编译通过
}
}
自定义异常:
所有异常都必须是 Throwable 的子类。
如果希望写一个检查性异常类,则需要继承 Exception 类。
如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
例:
class MyException extends Exception {
public MyException() {
}
public MyException(String message) {
super(message);
}
}
public class Main {
public static void main(String[] args) {
try {
throw new MyException("myException");
} catch (MyException e){
System.out.println(e.getMessage());
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
System.out.println("finally");
}
}
}
运行结果:
myException
finally
Process finished with exit code 0