Java的异常
- Throwable 是所有 Java 程序中错误处理的父类 ,有两种直接子类: Error 和 Exception。
1. Error错误
- Error 表示由 JVM 所侦测到的无法预期的错误,由于这是属于 JVM 层次的严重错误 ,导致 JVM 无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。
- 常见的Error:
- NotClassDefFountError(类未定义错误)
OutofMemoryError
(内存溢出错误)StackOverFlowError
(栈溢出错误)
2. Exception异常
- 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和非运行时异常
2.1 编译时异常
- 编译时异常又叫非运行时异常,或者受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会有此类异常时,将会提示你处理本异常,要么使用 try-catch 捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
- 常见的编译时异常:
IOException
:输入输出流异常SQLException
:数据库操作异常ClassNotFoundException
:类找不到的异常TimeoutException
:执行超时异常
2.2 运行时异常
-
运行时异常也叫非受检异常,是指编译通过但在程序运行时出现的异常。当出现这样的异常时,总是由虚拟机接管。
-
出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由 Thread.run()抛出 ,如果是单线程就被 main() 抛出 。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。
-
运行时异常是 Exception 的子类,也有一般异常的特点,是可以被 Catch 块处理的,不过一般情况下我们不对其进行处理。也就是说,如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。、
-
常见的运行时异常:
NullPointerException
: 空指针引用异常- 调用 null 对象的实例方法。
- 访问或修改 null 对象的字段。
- 将 null 作为一个数组,获得其长度。
ClassCastException
:类型强制转换异常- 试图将对象强制转换为不是实例的子类
- ArithmeticException:算术运算异常
ArrayIndexOutOfBoundsException
:下标越界异常- IllegalArgumentException:传递非法参数异常。
ConcurrentModificationException
:并发修改异常
在提到ConcurrentModificationException时可以多聊一些,这个异常会比前面的一些异常显得高级一点,让面试官知道你学习过JUC中的安全集合,确实是自己实践验证过。如果对有兴趣了解更多关于并发修改异常的知识,可以看我的这篇文章:JUC集合线程安全
3. 关于异常的面试题
3.1 Error 和 Exception 区别是什么?
- Error 类型的错误通常为虚拟机相关错误,如内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复。
- Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
3.2 运行时异常和编译时异常(受检异常)区别是什么?
- 运行时异常包括 RuntimeException 类及其子类,表示程序在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
- 受检异常是 Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
- 区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。
3.3 JVM 是如何处理异常的?
- 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称、异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
- JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM发现可以处理异常的代时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
3.4 throw 和 throws 的区别是什么?
- Java 中的异常处理除了包括捕获异常和处理异常(try-catch)之外,还包括声明异常和拋出异常(throws),可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。
- throws 关键字和 throw 关键字在使用上的几点区别如下:
- throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
- throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
3.5 final、finally、finalize 有什么区别?
- final 是一个关键字,可以修饰类、变量、方法。修饰类表示该类不能被继承,修饰方法表示该方法不能被重写,修饰变量表示该变量是一个常量不能被重新赋值。
- finally 一般作用在 try-catch 代码块中,在处理异常的时候,通常我们将一定要执行的代码方法 finally 代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
3.6 ClassNotFoundException 和 NoClassDefFoundError区别?
- ClassNotFoundException 是一个编译时异常,表示类未找到,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName、ClassLoader.loadClass、ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常。
- NoClassDefFoundError 是一个 Error 类型的错误,表示类未定义,是由 JVM 引起的,不应该尝试捕获这个异常。引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义。
- 总结:
- ClassNotFoundException 是编译时异常,表示类未找到;NoClassDefFoundError 是 Error 类型的错误,表示类未定义。
- ClassNotFoundException 需要用 try-catch 进行捕获或者使用 throws 抛出;NoClassDefFoundError 由 JVM 引起,不由我们去捕获或抛出。
- 产生异常的原因不一样
3.7 try-catch-finally 中哪个部分可以省略?
- 以下三种情况都是编译可以通过的:
try-catch
try-finally
try-catch-finally - 可以省略 catch 或者 finally。catch 和 finally 不可以同时省略。
3.8 如果 catch 中 return 了,finally 还会执行吗?
-
答案是会执行
- finally 的作用就是,无论出现什么状况,finally 里的代码一定会被执行。
- 如果在 catch 中 return了,也会在 return之前,先执行 finally 代码块。
- 而且如果 finally 代码块中含有 return 语句,会覆盖其他地方的 return。
- 对于基本数据类型的数据,在 finally 块中改变 return 的值对返回值没有影响,而对引用数据类型的数据会有影响。
-
基本数据类型的数据:
public class Test { public static void main(String[] args) { System.out.println(MyTest());//输出结果为20,说明在 finally 块中改变 return 的值对返回值没有影响 } public static int MyTest() { int a; try { a = 10; a = 10 / 0;//制造异常 } catch (Exception e) { e.printStackTrace(); a = 20; return a; } finally { a = 30; } return a; } }
-
引用类型的数据:
public class Test08 { public static void main(String[] args) { System.out.println(MyTest().getName());//输出结果为狗C,说明在 finally 块中改变 return 的值对引用数据类型的 数据会有影响 } public static Dog MyTest(){ Dog dog = new Dog(); try { dog.setName("狗A"); int a = 10 / 0;//制造异常 } catch (Exception e) { e.printStackTrace(); dog.setName("狗B"); return dog; } finally { dog.setName("狗C"); } return dog; } } class Dog{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }