详解面试中常被问到的那些异常问题

什么是异常?

所有使我们程序提前终止,或者没有向预想方向去执行的错误统称为异常。

异常的分类

所有异常都由Throwable 继承而来,即ExceptionError都继承自Throwable

注:Java中只有Throwable类型的实例才可以被抛出(throw)或者捕获(try..catch)

Error

Error是指在正常情况下不太可能出现的错误,它与代码的编写无关。通常是表示运行时JVM出现的问题,比如系统内部错误、或者资源不足等。

常见的有: OutOfMemoryError( 内存溢出 ) 、 VirtualMachineError(虚拟机错误)、 NoClassDefFoundError(类定义错误)

Exception

Exception是指程序运行时,可以预料到的错误,Exception又分为以下两类

  • 可检查异常 (Exception)

    需要我们在程序中必须进行显式的捕获处理,否则将会编译报错,比如常见的有IOException、SQLException

如下,是一个文件逐行读取的方法,如不进行异常处理,则编译报错。

 
  1. try {

  2. Thread.sleep(10);

  3. } catch (InterruptedException e) {

  4. e.printStackTrace();

  5. }

 
  1. public static void readFile(String fileName) {

  2. File file = new File(fileName);

  3. BufferedReader br = null;

  4. try {

  5. br = new BufferedReader(new FileReader(file));

  6. int line = 1;

  7. // 按行读取

  8. while (br.readLine() != null) {

  9. System.out.println(br.readLine());

  10. line++;

  11. }

  12. } catch (IOException e) {

  13. e.printStackTrace();

  14. }

  15. finally {

  16. if (br != null) {

  17. try {

  18. br.close();

  19. } catch (IOException e) {

  20. e.printStackTrace();

  21. }

  22. }

  23. }

  24. }

  • 不可检查异常 ( RuntimeException ):

    常见的有NullPointerException(空指针)、ArrayIndexOutOfBoundException(下标越界异常)。是否需要去捕捉,程序本身不要求我们强制去做这些。但是可以避免代码在运行过程中的逻辑错误。

如下,从集合中提取元素,本身不需要我们去进行异常捕捉,但是在运行的时候会报NullPointerException异常

 
  1. List lst = Arrays.asList("A", "B", "C");

  2. String str = lst.get(3);

注:

阿里Java开发手册中提到,如果可以进行预检测的RuntimeException不应该通过catch的方法进行处理。比如NullPointExceotion。可以先进行非控的判断,比如if(obj != null){…}

看了上面的内容,也就很轻松的去应对面试官问的问题 『Exception和Error有什么区别?』 当然,异常处理常作为面试过程中的夺命连环call。为了方便加深上面问题的答案,从网上找到了一张图。

详解面试中常被问到的那些异常问题_第1张图片

异常的处理

代码中的异常处理主要针对可检查异常。、

通过try…catch 语句来处理

 
  1. try {

  2. // 需要捕获的代码

  3. } catch (Exception e1) {

  4. // catch代码块

  5. }

  6. finally {

  7. // 无论是否发生异常,都会执行的代码块

  8. }

当try中代码块发生异常后,会执行catch代码块。但是无论如何,最终finally中的代码块一定会被执行,比如可以去做一些资源回收的工作。

2、通过throws/throw将异常抛出交给上层处理

  • throws

    用来声明一个方法可能产生的所有异常(并不一定会产生异常),不做任何处理就将异常往上传给调用者。

    用来方法声明后面,多个异常用逗号隔开,

     
    1. public String getValue(List list,int num) throws Exception{

    2. return (String) list.get(num);

    3. }

  • throw

    用来抛出一个具体的异常类型

    只是抛出异常对象吗,用在方法体内。一定会抛出异常。

 
  1. public static void main(String[] args) {

  2. try {

  3. throwNumException(11);

  4. } catch (Exception e) {

  5. System.out.println(e.getMessage());

  6. }

  7.  

  8. }

  9.  

  10. public static void throwNumException(int a) throws Exception{

  11. if (a > 10) {

  12. throw new Exception("a的数值不符合要求");

  13. }

  14. }

异常处理注意事项

1、谨慎使用异常处理

try..catch会造成程序的额外开销,所以建议只捕获关键的代码,切忌不要用try包住一个很大的代码块。

Java每实例化一个Exception,就会对当时的栈进行快照。如果操作比较频繁,那么对资源的开销就不能被忽略。

2、在finally代码块中对资源进行清理

如下代码,正常执行时没有任何问题,但是在关闭资源之前一旦出现异常,就会造成资源无法释放。

 
  1. File file = new File(fileName);

  2. BufferedReader br = null;

  3. try {

  4. br = new BufferedReader(new FileReader(file));

  5. // ignore 逻辑处理

  6. br.close(); //关闭资源

  7. } catch (IOException e) {

  8. e.printStackTrace();

  9. }

正确写法:

 
  1. try {

  2. br = new BufferedReader(new FileReader(file));

  3. // ignore 逻辑处理

  4. } catch (IOException e) {

  5. e.printStackTrace();

  6. }

  7. finally {

  8. if (br != null) {

  9. try {

  10. br.close();

  11. } catch (IOException e) {

  12. e.printStackTrace();

  13. }

  14. }

  15. }

3、指定具体的异常

以下代码在执行上并没有什么问题,但是捕获了一个通用异常Exception,正确写法应该捕获特定的异常。

Thread.sleep() 抛出的是InterruptedException。

 
  1. try {

  2. Thread.sleep(10);

  3. } catch (Exception e) {

  4. e.printStackTrace();

  5. }

在日常开发中,我们往往需要更直观的去观察异常,如果抛出多个异常,都用通用异常去捕获,这样就很那去定位问题的所在了。更严格的写法应该如下所示

 
  1. try {

  2. // ignore 逻辑代码

  3. Thread.sleep(10);

  4. } catch (InterruptedException e) {

  5. e.printStackTrace();

  6. } catch (Exception e) {

  7. e.printStackTrace();

  8. }

(以下几点摘自泰山版阿里开发手册,可以在公众号“Java专栏”后台回复 【阿里】)获取

4、不要用异常做流程控制,条件控制

异常设计的初衷是为了解决程序运行中的各种意外情况,切异常处理的效率比条件判断的要低很多

5、catch时,请分清稳定代码和不稳定代码

稳定代码指的是无论如果都不会再出错的代码,对于非稳定代码进行catch时,尽可能区分异常类型,再做对应的异常处理。

6、捕获就要处理

捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请
将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的
内容。

面试

以上基本包含了常见的面试题答案,另外常见的一个面试题还有 finally和try中的return哪个先执行?

可以先看看下面几个例子

1、

 
  1. public static int getNum() {

  2. int a = 10;

  3. try {

  4. System.out.println("进入try");

  5. return a;

  6. } catch (Exception e) {

  7. e.printStackTrace();

  8. return a;

  9. }

  10. finally {

  11. a = 20;

  12. System.out.println("a的值为:" + a);

  13. }

  14. }

执行结果:

 
  1. 进入try

  2. a的值为:20

  3. 10

结论:可以看出,进入try之后,在return之前执行了finally代码块的代码

2、

 
  1. public static void main(String[] args) {

  2. System.out.println(getNum());

  3.  

  4. }

  5.  

  6. public static int getNum() {

  7. int a = 10;

  8. try {

  9. System.out.println("执行try");

  10. //ignore

  11. System.out.println("即将return");

  12. return a;

  13. } catch (Exception e) {

  14. e.printStackTrace();

  15. return a;

  16. }

  17. finally {

  18. System.out.println("开始执行finally");

  19. a = 20;

  20. System.out.println("执行finally结束");

  21. }

  22. }

运行结果:

 
  1. 执行try

  2. 即将return

  3. 开始执行finally

  4. 执行finally结束

  5. 10

执行结果打印了10,似乎finally里中对a的修改并没有起到作用。在JVM规范中有这样写道

 
  1. If the try clause executes a return, the compiled code does the following:

  2. 1. Saves the return value (if any) in a local variable.

  3. 2. Executes a jsr to the code for the finally clause.

  4. 3. Upon return from the finally clause, returns the value saved in the local variable.

如果在try中return的情况下,先把try中将要return的值先存到一个本地变量中,即上面例子中的x=10将会被保存下来。接下来去执行finally语句,最后返回的是存在本地变量中的值,即返回x=10.

3、

 
  1. public static int getNum() {

  2. int a = 10;

  3. try {

  4. return a;

  5. } catch (Exception e) {

  6. e.printStackTrace();

  7. return a;

  8. }

  9. finally {

  10. a = 20;

  11. return a;

  12. }

  13. }

执行结果:

 
  1. 20

结论:如果finally中有return则会忽略try中的return

注:阿里开发手册中指出,不要再finally中使用return

总结:

  • try中有return, 会先将值暂存,无论finally语句中对该值做什么处理,最终返回的都是try语句中的暂存值。

  • 当try与finally语句中均有return语句,会忽略try中return。

你可能感兴趣的:(详解面试中常被问到的那些异常问题)