在JDK中的java.lang.Exception
包下为我们提供了非常多的异常类型
如数据库访问异常时应抛出SQLException
,操作阻塞超时时应抛出TimeoutException
,将给定字符串无法转换为Path
对象时应抛出InvalidPathException
为了满足需要,我们也可以创建自己的异常类,从而在进程中抛出特定类型的异常,这样做能够更好的对不同的异常类型进行区分,并提供更具针对性的错误信息.
异常的抛出和捕获
抛出和捕获是异常处理中的两个关键方式,他们的作用和功能是不同的.
抛出异常
指程序在运行过程中发生某些错误时,在方法头中使用throw
关键字来抛出一个异常对象,其目的是告知该方法的上层调用者在当前方法中的代码执行路径上发生了异常情况,这个方法可能无法继续正常执行下去,抛出异常会中断方法执行并传递异常信息给调用者
比如这里我们写一个简单的除数方法:
public static double divide(int a, int b) {
System.out.println("除数为: " + b);
return (double) a / b;
}
接下来我们在Main方法中使用divide(10, 0)
调用一下,这时我们发现,这时程序并不会报错,而是返回了一个Infinity
,这种情况显然是不允许出现在程序中的,因此我们应该对除零异常进行一些处理:
public static double divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
System.out.println("除数为: " + b);
return (double) a / b;
}
现在,当我们再次调用该方法,会发现程序会抛出异常并结束运行:
Exception in thread "main" java.lang.ArithmeticException: 除数不能为零
at com.syrize.Main.divide(Main.java:10)
at com.syrize.Main.main(Main.java:4)
捕获异常
在抛出异常后,显然整个程序就停止了运行,这种情况下如果我们想在抛出异常的情况下保证程序的继续执行或者对错误采取一些补救措施就有需要将异常捕获,对于可能抛出异常的代码块,使用try-catch
来进行捕获,在调用divide
方法的Main
方法中我们进行如下修改:
public static void main(String[] args) {
double result = 0;
try {
result = divide(10, 0);
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("捕获到异常: " + e.getMessage());
}
System.out.println("运行结束");
}
此时我们再次运行,控制台则会输出
捕获到异常: 除数不能为零
运行结束
这意味着我们的异常被catch
块捕获并输出了我们在方法中自定义的异常信息,而其后的语句在异常被捕获处理完毕后继续执行了.
自定义异常
首先,我们应创建一个类用于定义我们的异常,这个类必须继承自Exception
类或其子类,并根据Exception
中定义的构造方法照搬到我们自定义的异常类中:
public class DivideByZeroException extends Exception {
public DivideByZeroException() {
}
public DivideByZeroException(String message) {
super(message);
}
}
这样我们就完成了一个自定义的异常类型,显然,他应该在除法运算中除数为零的时候被抛出,现在我们写一个新的方法,这个方法将抛出DivideByZeroException
异常:
public static double otherDivide(int a, int b) throws DivideByZeroException {
if (b == 0) {
throw new DivideByZeroException("除数不能为零");
}
System.out.println("除数为: " + b);
return (double) a / b;
}
不过这里还有一个问题,那就是我们自定义的这个异常类是派生自Exception
类的,虽然并不妨碍我们的使用,但是为了便于理解,应该保证异常层级继承的体系是基于JDK已定义的异常类型,比如这一除零异常就应继承自ArithmeticException
,也就是在计算时发生异常算数条件时抛出的异常.注意该类只有两个构造方法,与Exception
类有所不同.
什么是应该被抛出的异常
当程序在执行过程中发现某个参数不满足,亦或者是运行期间发生了意料之外的状况,从而导致程序无法继续往后正常执行的异常都应该被抛出,比如上面演示过的除数为零,或是数组索引越界,空指针等等.
另一种情况,当传入方法参数不符合要求或无法被转换时,也应该被抛出,如传入负数作为日期或月份的值,传入不符合路径格式的字符串转换为Path对象时等等.
相反的,有些异常不应被抛出,例如想知道一个ArrayList
中是否存在某个值,此时应该使用contains()
方法来进行判断,而不是通过捕获IndexOutOfBoundsException
来进行处理.
还有一部分异常是由于系统自身的原因导致的,比如运行内存不足,磁盘空间不足,IO错误等等,这些异常不应该在程序中被我们捕获,而是应该交给JVM或系统来进行处理.
小结
- 抛出异常时,最好复用JDK里面已经定义好的异常类型
- 自定义异常时,最好派生自
RuntimeException
或其子类来提高可读性 - 异常处理应该用于运行时出错的状况,而非作为程序正常运行流程中的一部分来进行处理
- 不要滥用异常处理来控制执行流程,应优先考虑使用条件判断来处理业务逻辑