目录
Java Web面试题目录清单(高频面试题型)(点击进入…)
Java异常、Error(错误)和Exception(异常)高频面试题
- 目录
- Java异常、Error(错误)和Exception(异常)高频面试题
- 1.Java异常简介
- 2.Throwable(Java语言所有错误与异常超类)
- 3.Error(错误)
- 4.Exception(异常)
- 5.受检异常与非受检异常
- 6.Java异常关键字
- 7.Java异常处理
- 8.如何选择异常类型
- 9.Error和Exception区别?
- 10.运行时异常和一般异常(受检异常)区别?
- 11.JVM如何处理异常?
- 12.throw和throws区别?
- 13.NoClassDefFoundError和ClassNotFoundException区别?
- 14.try-catch-finally中哪个部分可以省略?
- 15.try-catch-finally中,如果catch中return了,finally还会执行吗?
- 16.类ExampleA继承Exception,类ExampleB继承ExampleA。请问执行此段代码的输出是什么?
- 17.常见的RuntimeException有哪些?
- 18.Java常见异常有哪些
- 19.在finally块中清理资源或者使用try-with-resource语句
- 20.使用finally代码块
- 21.Java 7的try-with-resource语法
- 22.优先明确的异常
- 23.对异常进行文档说明
- 24.使用描述性消息抛出异常
- 25.优先捕获最具体的异常
- 26.不要捕获Throwable类
- 27.不要忽略异常
- 28.不要记录日志并抛出异常
- 29.包装异常时不要抛弃原始的异常
- 30.不要使用异常控制程序的流程
- 31.使用标准异常
- 32.异常会影响性能
Java异常、Error(错误)和Exception(异常)高频面试题
1.Java异常简介
Java异常是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性
在有效使用异常的情况下,异常能清晰的回答what、where、why这3个问题:
(1)异常类型回答了“什么”被抛出
(2)异常堆栈跟踪回答了“在哪”抛出
(3)异常信息回答了“为什么”会抛出
2.Throwable(Java语言所有错误与异常超类)
包含两个子类:Error(错误)和Exception(异常),通常用于指示发生了异常情况
Throwable包含了其线程创建时线程执行堆栈的快照,提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息
3.Error(错误)
定义:Error类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误
特点:此类错误一般表示代码运行时JVM出现问题
异常 | 描述 |
---|---|
Virtual MachineError | 虚拟机运行错误 |
NoClassDefFoundError | 类定义错误 |
OutOfMemoryError | 内存不足错误 |
StackOverflowError | 栈溢出错误 |
此类错误发生时,JVM将终止线程
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类
4.Exception(异常)
程序本身可以捕获并且可以处理的异常
Exception这种异常又分为两类:①运行时异常和②编译时异常
(1)运行时异常
定义:RuntimeException类及其子类,表示JVM在运行期间可能出现的异常
特点:Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既“没有通过throws声明抛出它”,也“没有用try-catch语句捕获它”,还是会编译通过
异常 | 描述 |
---|---|
NullPointerException | 空指针异常 |
ArrayIndexOutBoundException | 数组下标越界异常 |
ClassCastException | 类型转换异常 |
ArithmeticExecption | 算术异常 |
此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然Java编译器不会检查运行时异常,但是也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免
例如:若会发生除数为零的情况,则需要通过代码避免该情况的发生
RuntimeException异常会由Java虚拟机自动抛出并自动捕获(就算没写异常捕获语句运行时也会抛出错误),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码
(2)编译时异常
定义:Exception中除RuntimeException及其子类之外的异常
特点:Java编译器会检查它。如果程序中出现此类异常
异常 | 描述 |
---|---|
ClassNotFoundException | 没有找到指定的类异常 |
IOException | IO流异常 |
①要么通过throws进行声明抛出
②要么通过try-catch进行捕获处理
否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常必须手动在代码里添加捕获语句来处理该异常
5.受检异常与非受检异常
Java所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。
(1)受检异常
编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除RuntimeException及其子类外,其他的 Exception异常都属于受检异常。
编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示处理本异常:
①要么使用try-catch捕获
②要么使用方法签名中用throws关键字抛出,否则编译不通过
(2)非受检异常
编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)
6.Java异常关键字
异常关键字 | 描述 |
---|---|
try | 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出 |
catch | 用于捕获异常。catch用来捕获try语句块中发生的异常 |
finally | finally语句块总是会被执行。主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止 |
throw | 用于抛出异常 |
throws | 用在方法签名中,用于声明该方法可能抛出的异常 |
7.Java异常处理
Java通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理
Java异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally
在Java应用中,异常的处理机制分为①声明异常、②抛出异常、③捕获异常
(1)声明异常(throws)
通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用throws关键字声明可能会抛出的异常
注意:非检查异常(Error、RuntimeException或它们的子类)不可使用throws关键字来声明要抛出的异常
一个方法出现编译时异常,就需要try-catch/ throws处理,否则会导致编译错误
(2)抛出异常(throw)
如果觉得解决不了某些异常问题,且不需要调用者处理,那么可以抛出异常。
throw关键字作用是在方法内部抛出一个Throwable类型的异常。任何Java代码都可以通过throw语句抛出异常。
(3)捕获异常(try-catch)
程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理
8.如何选择异常类型
可以根据下图来选择是捕获异常,声明异常还是抛出异常
9.Error和Exception区别?
异常 | 描述 |
---|---|
Error | 通常为虚拟机相关错误。如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复 |
Exception | 可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行 |
10.运行时异常和一般异常(受检异常)区别?
运行时异常包括RuntimeException类及其子类,表示JVM在运行期间可能出现的异常。Java编译器不会检查运行时异常。
受检异常是Exception中除RuntimeException及其子类之外的异常。Java编译器会检查受检异常
RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,建议使用RuntimeException异常
11.JVM如何处理异常?
在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给JVM的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
JVM会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM发现可以处理异常的代码时,会把发生的异常传递给它。如果JVM没有找到可以处理该异常的代码块,JVM就会将该异常转交给默认的异常处理器(默认处理器为JVM的一部分),默认异常处理器打印出异常信息并终止应用程序
12.throw和throws区别?
Java中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过throws关键字在方法上声明该方法要拋出的异常,或者在方法内部通过throw拋出异常对象
throws | throw |
---|---|
用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常 | 用在方法程序内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出 |
13.NoClassDefFoundError和ClassNotFoundException区别?
(1)NoClassDefFoundError
一个Error类型的异常,由JVM引起的,不应该尝试捕获这个异常
引起该异常的原因是JVM或ClassLoader尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是编译后被删除了等原因导致;
(2)ClassNotFoundException
一个受检查异常,需要显式地使用try-catch对其进行捕获和处理,或在方法签名中用throws关键字进行声明。当使用Class.forName、ClassLoader.loadClass或ClassLoader.findSystemClass动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它
14.try-catch-finally中哪个部分可以省略?
catch和finally语句块可以省略其中一个;try只适合处理运行时异常、try+catch适合处理运行时异常+普通异常
只用try去处理普通异常却不加以catch处理(编译通不过)。编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。运行时异常在编译时没有如此规定,所以catch可以省略,加上catch编译器也觉得无可厚非
编译器看任何代码都不顺眼,都觉得可能有潜在的问题。即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理
普通异常:编译器要求必须用catch捕获以便进一步处理
运行时异常:捕获然后丢弃;并且+finally扫尾处理,或者加上catch捕获以便进一步处理
15.try-catch-finally中,如果catch中return了,finally还会执行吗?
会执行,在return执行之后,返回结果之前执行
注意:在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。显然,在finally中返回或者修改返回值会对程序造成很大的困扰,Java中可以通过提升编译器的语法检查级别来产生警告或错误
16.类ExampleA继承Exception,类ExampleB继承ExampleA。请问执行此段代码的输出是什么?
try {
throw new ExampleB("b");
} catch (ExampleA e) {
System.out.println("ExampleA");
} catch (Exception e) {
System.out.println("Exception");
}
输出:ExampleA
根据里氏代换原则:能使用父类型的地方一定能使用子类型
抓取ExampleA类型异常的catch块能够抓住try块中抛出的ExampleB类型的异常)
17.常见的RuntimeException有哪些?
异常 | 描述 |
---|---|
ClassCastException | 类转换异常 |
IndexOutOfBoundsException | 数组越界 |
NullPointerException | 空指针 |
ArrayStoreException | 数据存储异常,操作数组时类型不一致 |
BufferOverflowException | IO异常 |
18.Java常见异常有哪些
异常 | 描述 |
---|---|
IllegalAccessError | 违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常 |
InstantiationError | 实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常 |
OutOfMemoryError | 内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误 |
StackOverflowError | 堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误 |
ClassCastException | 类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常 |
ClassNotFoundException | 找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常 |
ArithmeticException | 算术条件异常。譬如:整数除零等 |
ArrayIndexOutOfBoundsException | 数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出 |
IndexOutOfBoundsException | 索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常 |
InstantiationException | 实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常 |
NoSuchFieldException | 属性不存在异常。当访问某个类的不存在的属性时抛出该异常 |
NoSuchMethodException | 方法不存在异常。当访问某个类的不存在的方法时抛出该异常 |
NullPointerException | 空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等 |
NumberFormatException | 数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常 |
StringIndexOutOfBoundsException | 字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常 |
19.在finally块中清理资源或者使用try-with-resource语句
当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。
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);
}
问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try代码块内代码会正常执行,并且资源可以正常关闭。try代码块中一般都会调用一个或多个可能抛出异常的方法,这意味着代码可能不会执行到try代码块的最后部分。结果就是,并没有关闭资源。所以,应该把清理工作的代码放到finally里去,或者使用try-with-resource特性
20.使用finally代码块
与前面几行try代码块不同,finally代码块总是会被执行。不管try代码块成功执行之后还是在catch代码块中处理完异常后都会执行。因此,可以确保清理了所有打开的资源
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);
}
}
}
21.Java 7的try-with-resource语法
如果资源实现了AutoCloseable接口,可以使用这个语法。大多数的Java标准资源都继承了这个接口。当在try子句中打开资源,资源会在try代码块执行后或异常处理后自动关闭
File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file);) {
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
22.优先明确的异常
抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常
因此需要保证提供给他们尽可能多的信息。这样API更容易被理解。方法的调用者能够更好的处理异常并且避免额外的检查。因此,总是尝试寻找最适合你的异常事件的类。
例如:抛出一个NumberFormatException来替换一个IllegalArgumentException。避免抛出一个不明确的异常
23.对异常进行文档说明
当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。
在Javadoc添加@throws声明,并且描述抛出异常的场景
24.使用描述性消息抛出异常
在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等
但这里并不是说要对错误信息长篇大论,因为本来Exception的类名就能够反映错误的原因,因此只需要用一到两句话描述即可
如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是NumberFormatException。当你以错误的格式提供String时,它将被java.lang.Long类的构造函数抛出
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
25.优先捕获最具体的异常
大多数IDE都可以帮助实现这个最佳实践。当尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。但问题在于,只有匹配异常的第一个catch块会被执行。 因此,如果首先捕获IllegalArgumentException,则永远不会到达应该处理更具体的NumberFormatException的catch块,因为它是IllegalArgumentException的子类
总是优先捕获最具体的异常类,并将不太具体的catch块添加到列表的末尾
NumberFormatException是IllegalArgumentException的子类
try {
int parseInt = Integer.parseInt("one");
// 处理所有NumberFormatException异常(特定String转为数字格式化异常)
} catch (NumberFormatException e) {
System.out.println("NumberFormatException");
// log.error(e);
// 处理所有非NumberFormatException异常的IllegalArgumentException异常
} catch (IllegalArgumentException e) {
System.out.println("IllegalArgumentException");
// log.error(e);
}
26.不要捕获Throwable类
Throwable是所有异常和错误的超类。可以在catch子句中使用它,但是永远不应该这样做
如果在catch子句中使用Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM抛出错误,指出不应该由应用程序处理的严重问题。典型的例子是OutOfMemoryError或者StackOverflowError。两者都是由应用程序控制之外的情况引起的,无法处理。所以,最好不要捕获Throwable,除非确定自己处于一种特殊的情况下能够处理错误
try {
// do something
} catch (Throwable t) {
// don't do this!
}
27.不要忽略异常
很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志
try {
// do something
} catch (NumberFormatException e) {
// this will never happen
}
但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。
合理的做法是至少要记录异常的信息。
try {
// do something
} catch (NumberFormatException e) {
log.error("This should never happen: " + e);
}
28.不要记录日志并抛出异常
这可能是本文中最常被忽略的最佳实践。可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志
后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理
29.包装异常时不要抛弃原始的异常
捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理
在这样做时,请确保将原始异常设置为原因(注:参考下方代码NumberFormatException e中的原始异常e)。Exception类提供了特殊的构造函数方法,它接受一个Throwable作为参数。否则,将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
30.不要使用异常控制程序的流程
例如:本应该使用if语句进行条件判断的情况下,却使用异常处理,这是非常不好的习惯,会严重影响应用的性能
31.使用标准异常
如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用Java API提供的异常,如果标准的异常不能满足要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码
32.异常会影响性能
异常处理的性能成本非常高,每个Java程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能
(1)仅在异常情况下使用异常
(2)在可恢复的异常情况下使用异常
尽管使用异常有利于Java开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息