在程序开发中,异常处理也是我们经常使用的模块,但通常很少研究异常模块的一些知识点。比如,try-catch 要遵循的原则是什么,finally 为什么总能执行,try-catch 为什么要比较程序的执行性能和其他问题,我们会给出相应的答案,当然,有一些面试问题经常被问到的异常模块,这也是我们本文的关键内容。
介绍异常处理的基本介绍先来看看异常处理的语法格式:
try{ ... } catch(Exception e){ ... } finally{ ... }
其中,
- try:用于监控可能出现异常的代码段。
- catch:是用来捕获 try 如果代码块中某些代码引起的异常 try 里面没有异常发生, catch 也不会执行。在 Java 语言中,try 后面可以有多个 catch 用于捕捉不同类型异常的代码块,需要注意的是前面的 catch 捕获异常类型不得包括以下异常类型,因此编译器会报告错误。
- finally:不论 try-catch 如何执行,finally 它必须是最终执行的代码块,通常用于处理一些资源的释放,如关闭数据库连接、关闭开放系统资源等。
异常处理的基本使用可参考以下代码段:
try { int i = 10 / 0;} catch (ArithmeticException e) { System.out.println(e);} finally { System.out.println("finally");}
多 catch 具体使用可参考以下代码段:
try { int i = Integer.parseInt(null);} catch (ArithmeticException ae) { System.out.println("ArithmeticException");} catch (NullPointerException ne) { System.out.println("NullPointerException");} catch (Exception e) { System.out.println("Exception");}
需要注意的是 Java 虚拟机会从上到下匹配错误的类型,所以前面 catch 异常类型不能包含以下异常类型。例如,如果将上述代码放在上面 Exception 将编译器放在前面会报错,具体请参考下图。
发展异常处理随着 Java 语言的发展,JDK 7 引入一些更方便的特点,更方便处理异常信息,如 try-with-resources 和 multiple catch,详见以下代码段:
try (FileReader fileReader = new FileReader(""); FileWriter fileWriter = new FileWriter("")) { // try-with-resources System.out.println("try");} catch (IOException | NullPointerException e) { // multiple catch System.out.println(e);}
异常处理的基本原则先看下面的代码,有没有发现一些问题?
try { // ... int i = Integer.parseInt(null);} catch (Exception e) {}
上述代码看似“正常”,却违反了异常处理的两个基本原则:
- 第一,尽量不要捕捉一般的异常,就像 Exception 这种异常应该捕获特定的异常,这对你发现问题更有帮助;
- 第二,不要忽视异常。例如,上面的代码只添加了 catch,但没有如何处理错误,信息已经输出,所以当程序出现问题时,根本找不到问题的原因,所以记住不要直接忽视异常。
异常处理很容易使用,但不能滥用,如以下代码片段:
// 使用 com.alibaba.fastjsonJSONArray array = new JSONArray();String jsonStr = "{'name':'laowang'}";try { array = JSONArray.parseArray(jsonStr);} catch (Exception e) { array.add(JSONObject.parse(jsonStr));}System.out.println(array.size());
这个代码是借助的 try-catch 由于以下两个方面,处理程序的业务逻辑通常是不可取的。
- try-catch 代码段会产生额外的性能费用,或者从另一个角度来看,它往往会受到影响 JVM 优化代码,所以建议只捕获必要的代码段,尽量不要大 try 包裹整段代码;同时,使用异常控制代码流程也不是一个好主意,远远超过我们通常意义上的条件句(if/else、switch)要低效。
- Java 每实例化一个 Exception,当时的栈都会发快照,这是一个比较重的操作。假如发生得很频繁,这个费用就不能忽视了。
以上使用 try-catch 可将处理业务的代码修改为以下代码:
// 使用 com.alibaba.fastjsonJSONArray array = new JSONArray();String jsonStr = "{'name':'laowang'}";if (null != jsonStr && !jsonStr.equals("")) { String firstChar = jsonStr.substring(0, 1); if (firstChar.equals(“{”) { array.add(JSONObject.parse(jsonStr)); } else if (firstChar.equals([]) { array = JSONArray.parseArray(jsonStr); }}System.out.println(array.size());
相关面试题1. try 可以单独使用吗?答:try 不能单独使用,否则会丢失 try 意义和价值。
2. 以下 try-catch 能正常运行吗?try { int i = 10 / 0;} catch { System.out.println("last");}
答:不能正常运行,catch 以后必须包含异常信息,如 catch (Exception e)。
3. 以下 try-finally 能正常运行吗?try { int i = 10 / 0;} finally { System.out.println("last");}
答:可以正常运行。
4. 以下代码 catch 异常也发生在里面。程序将如何执行?try { int i = 10 / 0; System.out.println("try");} catch (Exception e) { int j = 2 / 0; System.out.println("catch");} finally { System.out.println("finally");}System.out.println("main");
答:程序会打印出来 finally 抛出异常,终止运行。
5. 以下代码 finally 异常也发生在里面。程序将如何运行?try { System.out.println("try");} catch (Exception e) { System.out.println("catch");} finally { int k = 3 / 0; System.out.println("finally");}System.out.println("main");
答:程序在输出 try 抛出异常并终止运行后,将不再执行 finally 代码异常后。
6. 常见操作有哪些异常?答:常见操作异常如下:
- java.lang.NullPointerException 空指针异常;原因:调用未初始化的对象或不存在的对象;
- java.lang.ClassNotFoundException 找不到指定类别;原因:类别名称和路径加载错误,通常是程序
试图通过字符串加载特定类别引起的异常;
- java.lang.NumberFormatException 字符串转换为数字异常;原因:非数字字符包含在字符数据中;
- java.lang.IndexOutOfBoundsException 操作数组对象时常见数组角标越界异常;
- java.lang.ClassCastException 数据类型转换异常;
- java.lang.NoClassDefFoundException 未发现类定义错误;
- java.lang.NoSuchMethodException 方法无异常;
- java.lang.IllegalArgumentException 传递参数错误的方法。
答:Exception 和 Error 都属于 Throwable 的子类,在 Java 中只有 Throwable 它们可以被捕获或抛出,它们之间的区别如下:
- Exception(异常)是程序正常运行时可以预测的事故,可以使用 try/catch 捕获处理。Exception 又分为运行异常(Runtime Exception)和异常检查(Checked Exception),异常编译可以在运行过程中通过,但如果在运行过程中出现这种未处理的异常,程序将终止运行;异常检查或使用 try/catch 捕获,或使用 throws 抛出字句声明,否则,编译将不会通过。
- Error(错误)是指突发异常情况,通常无法恢复,如 Java 虚拟机内存溢出等问题称为 Error。
答:它们之间的区别如下:
- throw 语句用于方法体内,表示抛出异常由方法体内的语句处理并执行 throw 必须抛出某种异常;
- throws 语句用在方法声明后面,该方法的调用者应处理异常,throws 代表可能有某种异常,不一定有这种异常。
答:Integer.parseInt(null) 和 Double.parseDouble(null) 抛出的异常类型不同,如下:
- Integer.parseInt(null) 抛出的异常是 NumberFormatException;
- Double.parseDouble(null) 抛出的异常是 NullPointerException。
至于为什么会出现不同的异常,没有特殊的原因,主要是因为这两个功能是由不同的人开发的,所以产生了两种不同的异常信息。
10. NoClassDefFoundError 和 ClassNoFoundException 有什么区别?- NoClassDefFoundError 是 Error(错误)类型,而 ClassNoFoundExcept 是 Exception(异常)类型;
- ClassNoFoundExcept 是 Java 使用 Class.forName 动态加载方法,如果不加载,就会抛出 ClassNoFoundExcept 异常;
- NoClassDefFoundError 是 Java 虚拟机或者 ClassLoader 当你试图加载类时,你找不到类订阅,也就是说,你想找到的类在编译中存在,但在操作中找不到,这次会出现 NoClassDefFoundError 的错误。
答:这个问题要从 JVM(Java 在虚拟机层面找到答案。首先 Java 虚拟机在构建异常实例时需要生成异常栈轨迹。此操作将逐一访问当前线程的栈帧,并记录各种调试信息,包括栈帧指向方法的名称、类别名称、文件名称和代码中的第一行触发异常信息,这是使用异常捕获耗时的主要原因。
12. 常见的 OOM 原因是什么?答:常见的 OOM 原因如下:
- 未关闭数据库资源;
- 加载特别大的图片;
- 递归次数过多,未释放的变量一直在操作。
public static int getNumber() { try { int number = 0 / 1; return 2; } finally { return 3; }}
A:0
B:2
C:3
D:1
答:3
主题分析:最终将执行程序 finally 内部代码将以前的结果覆盖为 3。
14. finally、finalize 有什么区别?答:finally、finalize 区别如下:
- finally 它是语句处理异常的一部分,意味着它总是执行的;
- finalize 是 Object 一种方法是子类可以覆盖这种方法来实现资源清理,这种方法将在垃圾回收前调用。
答:finally 编译器的作用总是执行的,因为编译器在编译中 Java 当代码被复制时 finally 代码块的内容,然后分别放置 try-catch 在代码块所有正常执行路径和异常执行路径的出口中, finally 无论发生什么情况,都会执行。