目录
1. 总览
1.1 Exception 与 Error
1.2 checked unchecked 异常
1)使用 try-catch 进行捕获
2)使用 throws 关键字抛出
1.3 throw 与 throws
1)throw
2)throws
3)区别
1.4 try-catch-finally
2. try with resources
2.1 try–catch-finally 不足
2.2 解决问题
3. 异常处理实践
3.1 尽量不要捕获 RuntimeException
3.2 尽量使用 try-with-resource 来关闭资源
3.3 不要捕获 Throwable
3.4 不要省略异常信息的记录
3.5 不要记录了异常又抛出异常
3.6 不要在 finally 块中使用 return
3.7 抛出具体定义的检查性异常而不是 Exception
3.8 捕获具体的子类而不是捕获 Exception 类
3.9 自定义异常时不要丢失堆栈跟踪
3.10 finally 块中不要抛出任何异常
3.11 不要在生产环境中使用 printStackTrace()
3.12 对于不打算处理的异常,直接使用 try-finally,不用 catch
3.13 记住早 throw 晚 catch 原则
3.14 只抛出和方法相关的异常
3.15 切勿在代码中使用异常来进行流程控制
3.16 尽早验证用户输入以在请求处理的早期捕获异常
3.17 一个异常只能包含在一个日志中
3.18 将所有相关信息尽可能地传递给异常
3.19 终止掉被中断线程
3.20 对于重复的 try-catch,使用模板方法
1. 总览
异常是指中断程序正常执行的一个不确定的事件
有了异常处理机制后,程序在发生异常的时候就不会中断,我们可以对异常进行捕获,然后改变程序执行的流程
1.1 Exception 与 Error
Exception 和 Error 都继承了 Throwable 类
NoClassDefFoundError 和 ClassNotFoundException 有什么区别?
都是由于系统运行时找不到要加载的类导致的,但是触发的原因不一样
- NoClassDefFoundError:程序在编译时可以找到所依赖的类,但是在运行时找不到指定的类文件;原因:可能是 jar 包缺失或者调用了初始化失败的类
- ClassNotFoundException:当动态加载 Class 对象的时候找不到对应的类时抛出该异常;原因:要加载的类不存在或者类名写错了
Error 的出现,意味着程序出现了严重的问题,而这些问题不应该再交给 Java 的异常处理机制来处理,程序应该直接崩溃掉
Exception 的出现,意味着程序出现了一些在可控范围内的问题,应当采取措施进行挽救
1.2 checked unchecked 异常
checked 异常(检查型异常)在源代码里必须显式地捕获或者抛出,否则编译器会提示你进行相应的操作
unchecked 异常(非检查型异常)就是运行时异常,通常是可以通过编码进行规避的,并不需要显式地捕获或者抛出
1)使用 try-catch 进行捕获
try {Class clz = Class.forName("com.itwanger.s41.Demo1");
} catch (ClassNotFoundException e) {e.printStackTrace();
}
注:该方法会将异常的堆栈信息打印到标准的控制台下;如果是生产环境,必须使用日志框架把异常的堆栈信息输出到日志系统中,否则可能没办法跟踪
2)使用 throws 关键字抛出
public class Demo1 {public static void main(String[] args) throws ClassNotFoundException {Class clz = Class.forName("com.itwanger.s41.Demo1");}
}
这样做的好处是不需要对异常进行捕获处理,只需要交给 Java 虚拟机来处理即可
坏处就是没法针对这种情况做相应的处理
1.3 throw 与 throws
1)throw
throw 关键字,用于主动地抛出异常
throw new exception_class("error message");
2)throws
throws 与 try-catch 对比
public static void main(String args[]){try {myMethod1();} catch (ArithmeticException e) {// 算术异常} catch (NullPointerException e) {// 空指针异常}
}
public static void myMethod1() throws ArithmeticException, NullPointerException{// 方法签名上声明异常
}
3)区别
- throws 关键字用于声明异常,它的作用和 try-catch 相似;而 throw 关键字用于显式的抛出异常。
- throws 关键字后面跟的是异常的名字;而 throw 关键字后面跟的是异常的对象
- throws 关键字出现在方法签名上,而 throw 关键字出现在方法体里
- throws 关键字在声明异常的时候可以跟多个,用逗号隔开;而 throw 关键字每次只能抛出一个异常
1.4 try-catch-finally
try {// 可能发生异常的代码
}catch {// 异常处理
}finally {// 必须执行的代码
}
finally 块常用来关闭一些连接资源,比如说 socket、数据库链接、IO 输入输出流等
OutputStream osf = new FileOutputStream( "filename" );
OutputStream osb = new BufferedOutputStream(opf);
ObjectOutput op = new ObjectOutputStream(osb);
try{output.writeObject(writableObject);
} finally{op.close();
}
注:即使 try 块有 return,finally 块也会执行
当然也存在 不执行 finally 的情况:
- 死循环
- 执行了 System.exit()
System.exit() 与 return 不同,前者是用来退出程序的,后者只是回到了上一级方法调用
2. try with resources
2.1 try–catch-finally 不足
在 Java 7 之前,try–catch-finally 是确保资源会被及时关闭的最佳方法,无论程序是否会抛出异常
public class TrycatchfinallyDecoder {public static void main(String[] args) {BufferedReader br = null;try {String path = TrycatchfinallyDecoder.class.getResource("/a.txt").getFile();String decodePath = URLDecoder.decode(path,"utf-8");br = new BufferedReader(new FileReader(decodePath));String str = null;while ((str =br.readLine()) != null) {System.out.println(str);}} catch (IOException e) {e.printStackTrace();} finally {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}}
}
存在一个严重的隐患:try 与 finally 都有可能抛出 IOException,那么程序的调试任务就变得复杂了起来,因为不确定到底是哪一处出了错误
2.2 解决问题
try-with-resources 可以解决该问题,前提是需要释放的资源(比如 BufferedReader)实现了 AutoCloseable 接口,并提供 close() 方法即可
try (BufferedReader br = new BufferedReader(new FileReader(decodePath));) {String str = null;while ((str =br.readLine()) != null) {System.out.println(str);}
} catch (IOException e) {e.printStackTrace();
}
把要释放的资源写在 try 后的 () 里,如果有多个资源(BufferedReader 和 PrintWriter)需要释放的话,可以直接在 () 中添加(就像写成员属性定义的语句一样)
try (BufferedReader br = new BufferedReader(new FileReader(decodePath));PrintWriter writer = new PrintWriter(new File(writePath))) {String str = null;while ((str =br.readLine()) != null) {writer.print(str);}
} catch (IOException e) {e.printStackTrace();
}
如果想释放自定义资源的话,只要让它实现 AutoCloseable 接口,并提供 close() 方法即可
public class TrywithresourcesCustom {public static void main(String[] args) {try (MyResource resource = new MyResource();) {} catch (Exception e) {e.printStackTrace();}}
}class MyResource implements AutoCloseable {@Overridepublic void close() throws Exception {System.out.println("关闭自定义资源");}
}
本质:编译器主动为 try-with-resources 进行了变身,在 try 中调用了 close() 方法
好处:是不会丢失任何异常
3. 异常处理实践
异常处理实践经验
3.1 尽量不要捕获 RuntimeException
正例:
if (obj != null) {//...
}
反例:
try { obj.method();
} catch (NullPointerException e) {//...
}
3.2 尽量使用 try-with-resource 来关闭资源
当需要关闭资源时,尽量不要使用 try-catch-finally,禁止在 try 块中直接关闭资源
反例:
public void doNotCloseResourceInTry() {FileInputStream inputStream = null;try {File file = new File("./tmp.txt");inputStream = new FileInputStream(file);inputStream.close();} catch (FileNotFoundException e) {log.error(e);} catch (IOException e) {log.error(e);}
}
原因:一旦 close()之前发生异常,那么资源就无法关闭
正例:直接使用 try-with-resource
public void automaticallyCloseResource() {File file = new File("./tmp.txt");try (FileInputStream inputStream = new FileInputStream(file);) {} catch (FileNotFoundException e) {log.error(e);} catch (IOException e) {log.error(e);}
}
如果资源没有实现 AutoCloseable 接口,就在 finally 块关闭流
public void closeResourceInFinally() {FileInputStream inputStream = null;try {File file = new File("./tmp.txt");inputStream = new FileInputStream(file);} catch (FileNotFoundException e) {log.error(e);} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {log.error(e);}}}
}
3.3 不要捕获 Throwable
Throwable 是 exception 和 error 的父类,如果在 catch 子句中捕获了 Throwable,很可能把超出程序处理能力之外的错误也捕获了
反例:
public void doNotCatchThrowable() {try {} catch (Throwable t) {// 不要这样做}
}
3.4 不要省略异常信息的记录
需要记录错误信息
public void logAnException() {try {} catch (NumberFormatException e) {log.error("错误发生了: " + e);}
}
3.5 不要记录了异常又抛出异常
反例:
public void wrapException(String input) throws MyBusinessException {try {} catch (NumberFormatException e) {throw new MyBusinessException("错误信息描述:", e);}
}
3.6 不要在 finally 块中使用 return
try 块中的 return 语句执行成功后,并不会马上返回,而是继续执行 finally 块中的语句,如果 finally 块中也存在 return 语句,那么 try 块中的 return 就将被覆盖。
反例:
private int x = 0;
public int checkReturn() {try {return ++x;} finally {return ++x;}
}
3.7 抛出具体定义的检查性异常而不是 Exception
反例:
public void foo() throws Exception { //错误方式
}
声明的方法应该尽可能抛出具体的检查性异常
3.8 捕获具体的子类而不是捕获 Exception 类
反例:
try {someMethod();
} catch (Exception e) { //错误方式LOGGER.error("method has failed", e);
}
如果捕获 Exception 类型的异常,可能会导致以下问题:
- 难以识别和定位异常:如果捕获 Exception 类型的异常,可能会捕获到一些不应该被处理的异常,从而导致程序难以识别和定位异常
- 难以调试和排错:如果捕获 Exception 类型的异常,可能会使得调试和排错变得更加困难,因为无法确定具体的异常类型和异常发生的原因
正例:应该尽可能地捕获具体的子类
try {// 读取数据的代码
} catch (FileNotFoundException e) {// 处理文件未找到异常的代码
} catch (IOException e) {// 处理输入输出异常的代码
}
3.9 自定义异常时不要丢失堆栈跟踪
不要破坏原始异常的堆栈跟踪
public class MyException extends Exception {public MyException(String message, Throwable cause) {super(message, cause);}@Overridepublic void printStackTrace() {System.err.println("MyException:");super.printStackTrace();}
}
3.10 finally 块中不要抛出任何异常
try {someMethod(); //Throws exceptionOne
} finally {cleanUp(); //如果finally还抛出异常,那么exceptionOne将永远丢失
}
如果在 finally 块中抛出异常,可能会导致原始异常被掩盖
一旦 cleanup 抛出异常,someMethod 中的异常将会被覆盖
3.11 不要在生产环境中使用 printStackTrace()
它可能会导致以下问题:
- printStackTrace() 方法将异常的堆栈跟踪信息输出到标准错误流中,这可能会暴露敏感信息,如文件路径、用户名、密码等。
- printStackTrace() 方法会将堆栈跟踪信息输出到标准错误流中,这可能会影响程序的性能和稳定性。在高并发的生产环境中,大量的异常堆栈跟踪信息可能会导致系统崩溃或出现意外的行为。
- 由于生产环境中往往是多线程、分布式的复杂系统,printStackTrace() 方法输出的堆栈跟踪信息可能并不完整或准确。
在生产环境中,应该使用日志系统来记录异常信息,日志系统可以将异常信息记录到文件或数据库中,而不会暴露敏感信息,也不会影响程序的性能和稳定性
// 可以使用 logback 记录异常信息,如下所示:
try {// some code
} catch (Exception e) {logger.error("An error occurred: ", e);
}
3.12 对于不打算处理的异常,直接使用 try-finally,不用 catch
try {method1(); // 会调用 Method 2
} finally {cleanUp(); //do cleanup here
}
3.13 记住早 throw 晚 catch 原则
在代码中尽可能早地抛出异常,以便在异常发生时能够及时地处理异常
在 catch 块中尽可能晚地捕获异常,以便在捕获异常时能够获得更多的上下文信息,从而更好地处理异常
3.14 只抛出和方法相关的异常
public class Demo {public static void main(String[] args) {try {int result = divide(10, 0);System.out.println("The result is: " + result);} catch (ArithmeticException e) {System.err.println("Error: " + e.getMessage());}}public static int divide(int a, int b) throws ArithmeticException {if (b == 0) {throw new ArithmeticException("Division by zero");}return a / b;}
}
只抛出了和方法相关的异常 ArithmeticException,这可以使代码更加清晰和易于维护
3.15 切勿在代码中使用异常来进行流程控制
使用异常来进行流程控制会导致代码的可读性、可维护性和性能出现问题
应该使用其他合适的控制结构来管理程序的流程
虽然是可以实现逻辑的,但是要避免这样使用
public class Demo {public static void main(String[] args) {String input = "1,2,3,a,5";String[] values = input.split(",");for (String value : values) {try {int num = Integer.parseInt(value);System.out.println(num);} catch (NumberFormatException e) {System.err.println(value + " is not a valid number");}}}
}
3.16 尽早验证用户输入以在请求处理的早期捕获异常
用 JDBC 的方式往数据库插入数据,那么最好是先 validate 再 insert
3.17 一个异常只能包含在一个日志中
反例:
log.debug("Using cache sector A");
log.debug("Using retry sector B");
在单线程环境中,这样看起来没什么问题,但如果在多线程环境中,这两行紧挨着的代码中间可能会输出很多其他的内容,导致问题查起来会很难
正例:
LOGGER.debug("Using cache sector A, using retry sector B");
3.18 将所有相关信息尽可能地传递给异常
有用的异常消息和堆栈跟踪非常重要
应该尽量把 String message, Throwable cause
异常信息和堆栈都输出
3.19 终止掉被中断线程
正例:
while (true) {try {Thread.sleep(100000);} catch (InterruptedException e) {break;}
}
doSomethingCool();
3.20 对于重复的 try-catch,使用模板方法
在尝试关闭数据库连接时的异常处理时使用模板方法:
class DBUtil{public static void closeConnection(Connection conn){try{conn.close();} catch(Exception ex){//Log Exception - Cannot close connection}}
}public void dataAccessCode() {Connection conn = null;try{conn = getConnection();....} finally{DBUtil.closeConnection(conn);}
}