java异常处理机制(实践建议篇)
转载自java全栈知识体系
https://www.cnblogs.com/dolphin0520/p/3769804.html
在java中对于异常的处理并不是一件简单的事情,可能需要经过大量的思考以确保其合理性,以便程序后期的维护。一般团队中会制定一些标准来规范对异常的处理 本文给出一些通用的建议
只针对不正常的情况才使用异常
异常只应该被用于不正常的条件,它们永远不应该被用于正常的控制流。《阿里手册》中:【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。
<<java核心技术>>:不应该用throws声明从RuntimeException继承的那些非检查型异常,这些运行时异常完成在我们的控制之中,如结果特别担心数组下标错误,就应该多花时间修正这些错误,而不是声明这些错误有可能发生
比如在在判断数组下标是否越界,空指针被使用,算术异常,类的类型转换等异常时,不得使用catch进行捕获处理,重在进行预检查
避免这些异常
不要用异常去控制程序的流程
谨慎地使用异常,异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。如果在程序中能够用if语句和Boolean变量来进行逻辑判断,那么尽量减少异常的使用,从而避免不必要的异常捕获和处理
一个经典程序
public void useExceptionsForFlowControl() {
try {
while (true) {
increaseCount();
}
} catch (MaximumCountReachedException ex) {
}
//Continue execution
}
public void increaseCount() throws MaximumCountReachedException {
if (count >= 5000)
throw new MaximumCountReachedException();
}
上边的useExceptionsForFlowControl()用一个无限循环来增加count直到抛出异常,这种做法并没有说让代码不易读,而是使得程序执行效率降低。
总结以上两点:对于异常,优先考虑用if判断处理,能不用异常处理语句尽量不用。对于运行时异常,尽量不进行异常处理,一般在我们的控制之内,在程序运行前进行预处理,将这些这些异常避免掉
优先捕捉最具体的异常
一个catch只能捕捉一个异常,如果首先捕获 IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
不要捕获 Throwable 类
最高只能捕捉到Exception类,不能捕捉Throwable,因为Throwable类可以接收所有的异常,但也可以接受所有的错误,捕捉到错误将直接交由JVM进行处理这样做一般会带来更加严重的错误
切忌使用空catch块
在捕捉到异常后不进行任何处理,相当于忽略了这个异常,可能自信该处不会发生任何异常,但这是一个非常不好的习惯。
千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。
合理的做法是至少要记录异常的信息。
try {
// do something
} catch (NumberFormatException e) {
log.error("This should never happen: " + e); // see this line
}
}
尽量使用标准的异常
代码重用是值得提倡的,这是一条通用规则,异常也不例外。
对于异常的重用对立的应该了自定义异常类,相比异常重用有以下好处:
-
它使得你的API更加易于学习和使用,因为它与程序员原来已经熟悉的习惯用法是一致的。
-
对于用到这些API的程序而言,它们的可读性更好,因为它们不会充斥着程序员不熟悉的异常。
-
异常类越少,意味着内存占用越小,并且转载这些类的时间开销也越小。**
如果一个异常满足你的需要,则不要犹豫,使用就可以,不过你一定要确保抛出异常的条件与该异常的文档中描述的条件一致。这种重用必须建立在语义的基础上,而不是名字的基础上。选择重用哪一种异常并没有必须遵循的规则
不要记录并抛出异常
可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下:
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下:
不要在finally块中使用return
finally关键字描述的是总是会被执行,当一个函数有返回值,而且此时finally语句中有retrun语句,就可能会改变该函数的返回值,
try{
int c=a/b;
return c;
}catch(ArithmeticException e){
System.out.println(e);
}finally{
return 0;
}
}
以上程序函数会最终返回0,而不会返回正确的答案
不要将提供给用户看的信息放在异常信息里
public static void main(String[] args) {
try {
String user = null;
String pwd = null;
login(user,pwd);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static void login(String user,String pwd) {
if(user==null||pwd==null)
throw new NullPointerException("用户名或者密码为空");
//...
}
}
展示给用户错误提示信息最好不要跟程序混淆一起,比较好的方式是将所有错误提示信息放在一个配置文件中统一管理。
在finally中释放资源
如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。