2023年11月29日发(作者:)

Java异常⾯试题

⽂章⽬录

Java异常架构与异常关键字

Java异常简介

Java异常是Java提供的⼀种识别及响应错误的⼀致性机制。

2. Error(错误)

定义:Error 类及其⼦类。程序中⽆法处理的错误,表⽰运⾏应⽤程序中出现了严重的错误。

特点:此类错误⼀般表⽰代码运⾏时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运⾏错误)、NoClassDefFoundError(类

定义错误)等。⽐如 OutOfMemoryError:内存不⾜错误;StackOverflowError:栈溢出错误。此类错误发⽣时,JVM 将终⽌线程。

这些错误是不受检异常,⾮代码性错误。因此,当此类错误发⽣时,应⽤程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任

何新的Error⼦类的!

Java 通过⾯向对象的⽅法进⾏异常处理,⼀旦⽅法抛出异常,系统⾃动根据该异常对象寻找合适异常处理器(Exception Handler)来处

理该异常,把各种不同的异常进⾏分类,并提供了良好的接⼝。在 Java 中,每个异常都是⼀个对象,它是 Throwable 类或其⼦类的实

例。当⼀个⽅法出现异常后便抛出⼀个异常对象,该对象中包含有异常信息,调⽤这个对象的⽅法可以捕获到这个异常并可以对其进⾏处

理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。

常见异常处理⽅式

直接抛出异常

通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在⽅法签名处使⽤ throws 关键字声明可

能会抛出的异常。

private static void readFile(String filePath) throws IOException {

File file = new File(filePath);

String result;

BufferedReader reader = new BufferedReader(new FileReader(file));

while((result = reader.readLine())!=null) {

System.out.println(result);

}

reader.close();

}

封装异常再抛出

有时我们会从 catch 中抛出⼀个异常,⽬的是为了改变异常的类型。多⽤于在多系统集成时,当某个⼦系统故障,异常类型可能有多种,

可以⽤统⼀的异常类型向外暴露,不需暴露太多内部异常细节。

private static void readFile(String filePath) throws MyException {

try {

// code

} catch (IOException e) {

MyException ex = new MyException("read file failed.");

ex.initCause(e);

throw ex;

}

}

捕获异常

在⼀个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理

private static void readFile(String filePath) {

try {

// code

} catch (FileNotFoundException e) {

// handle FileNotFoundException

} catch (IOException e){

// handle IOException

}

}

同⼀个 catch 也可以捕获多种类型异常,⽤ | 隔开

private static void readFile(String filePath) {

try {

// code

} catch (FileNotFoundException | UnknownHostException e) {

// handle FileNotFoundException or UnknownHostException

} catch (IOException e){

// handle IOException

}

}

⾃定义异常

习惯上,定义⼀个异常类应包含两个构造函数,⼀个⽆参构造函数和⼀个带有详细描述信息的构造函数(Throwable 的 toString ⽅法会打

印这些详细信息,调试时很有⽤)

public class MyException extends Exception {

public MyException(){ }

public MyException(String msg){

super(msg);

}

// ...

}

try-catch-finally

当⽅法中发⽣异常,异常处之后的代码不会再执⾏,如果之前获取了⼀些本地资源需要释放,则需要在⽅法正常结束时和 catch 语句中都

调⽤释放本地资源的代码,显得代码⽐较繁琐,finally 语句可以解决这个问题。

private static void readFile(String filePath) throws MyException {

File file = new File(filePath);

String result;

BufferedReader reader = null;

try {

reader = new BufferedReader(new FileReader(file));

while((result = reader.readLine())!=null) {

System.out.println(result);

}

} catch (IOException e) {

System.out.println("readFile method catch block.");

MyException ex = new MyException("read file failed.");

ex.initCause(e);

throw ex;

} finally {

System.out.println("readFile method finally block.");

if (null != reader) {

try {

reader.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

调⽤该⽅法时,读取⽂件时若发⽣异常,代码会进⼊ catch 代码块,之后进⼊ finally 代码块;若读取⽂件时未发⽣异常,则会跳过 catch

代码块直接进⼊ finally 代码块。所以⽆论代码中是否发⽣异常,fianlly 中的代码都会执⾏。

若 catch 代码块中包含 return 语句,finally 中的代码还会执⾏吗?将以上代码中的 catch ⼦句修改如下:

catch (IOException e) {

System.out.println("readFile method catch block.");

return;

}

调⽤ readFile ⽅法,观察当 catch ⼦句中调⽤ return 语句时,finally ⼦句是否执⾏

readFile method catch block.

readFile method finally block.

可见,即使 catch 中包含了 return 语句,finally ⼦句依然会执⾏。若 finally 中也包含 return 语句,finally 中的 return 会覆盖前⾯的

return.

try-with-resource

上⾯例⼦中,finally 中的 close ⽅法也可能抛出 IOException, 从⽽覆盖了原始异常。JAVA 7 提供了更优雅的⽅式来实现资源的⾃动释

放,⾃动释放的资源需要是实现了 AutoCloseable 接⼝的类。

private static void tryWithResourceTest(){

try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){

// code

} catch (IOException e){

// handle exception

}

}

try 代码块退出时,会⾃动调⽤ ⽅法,和把 ⽅法放在 finally 代码块中不同的是,若 抛出

异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由 addSusppressed ⽅法添加到原来的异常,如果想要获取被抑制的异常列

表,可以调⽤ getSuppressed ⽅法来获取。

Java异常常见⾯试题

1. Error Exception 区别是什么?

Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不⾜,堆栈溢出等,编译器不会对这类错误进⾏检测,JAVA 应⽤程序也不应

对这类错误进⾏捕获,⼀旦这类错误发⽣,通常应⽤程序会被终⽌,仅靠应⽤程序本⾝⽆法恢复;

Exception 类的错误是可以在应⽤程序中进⾏捕获并处理的,通常遇到这种错误,应对其进⾏处理,使应⽤程序可以继续正常运⾏。

2. 运⾏时异常和⼀般异常(受检异常)区别是什么?

运⾏时异常包括 RuntimeException 类及其⼦类,表⽰ JVM 在运⾏期间可能出现的异常。 Java 编译器不会检查运⾏时异常。

受检异常是Exception 中除 RuntimeException 及其⼦类之外的异常。 Java 编译器会检查受检异常。

RuntimeException异常和受检异常之间的区别:是否强制要求调⽤者必须处理此异常,如果强制要求调⽤者必须进⾏处理,那么就使⽤受

检异常,否则就选择⾮受检异常(RuntimeException)。⼀般来讲,如果没有特殊的要求,我们建议使⽤RuntimeException异常。

3. JVM 是如何处理异常的?

在⼀个⽅法中如果发⽣异常,这个⽅法会创建⼀个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发⽣时应⽤程

序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有⼀系列的⽅法调⽤,最终才进⼊抛出异常的⽅法,这⼀系列⽅法调⽤

的有序列表叫做调⽤栈。

JVM 会顺着调⽤栈去查找看是否有可以处理异常的代码,如果有,则调⽤异常处理代码。当 JVM 发现可以处理异常的代码时,会把发⽣

的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的⼀部

分),默认异常处理器打印出异常信息并终⽌应⽤程序。

4. throw throws 的区别是什么?

Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在⽅法上声明该⽅法要拋出

的异常,或者在⽅法内部通过 throw 拋出异常对象。

throws 关键字和 throw 关键字在使⽤上的⼏点区别如下:

throw 关键字⽤在⽅法内部,只能⽤于抛出⼀种异常,⽤来抛出⽅法或代码块中的异常,受查异常和⾮受查异常都可以被抛出。

原因

更为严格的说法其实是:try只适合处理运⾏时异常,try+catch适合处理运⾏时异常+普通异常。也就是说,如果你只⽤try去处理普通异常

却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须⽤catch显⽰声明以便进⼀步处理。⽽运⾏

时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得⽆可厚⾮。

理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运⾏期时也只不过是在正常运⾏

的基础上加⼀层⽪。但是你⼀旦对⼀段代码加上try,就等于显⽰地承诺编译器,对这段代码可能抛出的异常进⾏捕获⽽⾮向上抛出处理。

如果是普通异常,编译器要求必须⽤catch捕获以便进⼀步处理;如果运⾏时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕

获以便进⼀步处理。

⾄于加上finally,则是在不管有没捕获异常,都要进⾏的“扫尾”处理。

8. try-catch-finally 中,如果 catch return 了,finally 还会执⾏吗?

答:会执⾏,在 return 前执⾏。

注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会⽴马返回调⽤者,⽽是记录下返回

值待 finally 代码块执⾏完毕之后再向调⽤者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返

回或者修改返回值会对程序造成很⼤的困扰,C#中直接⽤编译错误的⽅式来阻⽌程序员⼲这种龌龊的事情,Java 中也可以通过提升编译器

的语法检查级别来产⽣警告或错误。

代码⽰例1:

public static int getInt() {

int a = 10;

try {

System.out.println(a / 0);

a = 20;

} catch (ArithmeticException e) {

a = 30;

return a;

/*

* return a return a return 30

在程序执⾏到这⼀步的时候,这⾥不是⽽是;这个返回路径就形成了

* finallyfinallya=40

但是呢,它发现后⾯还有,所以继续执⾏的内容,

* ,return 30aa30

再次回到以前的路径继续⾛,形成返回路径之后,这⾥的就不是变量了,⽽是常量

*/

} finally {

a = 40;

}

return a;

}

执⾏结果:30

代码⽰例2:

public static int getInt() {

int a = 10;

try {

System.out.println(a / 0);

a = 20;

} catch (ArithmeticException e) {

a = 30;

return a;

} finally {

a = 40;

//1return40

如果这样,就⼜重新形成了⼀条返回路径,由于只能通过返回,所以这⾥直接返回

return a;

}

}

执⾏结果:40

9. ExampleA 继承 Exception,类 ExampleB 继承ExampleA

有如下代码⽚断:

try {

throw new ExampleB("b")

} catchExampleA e{

System.out.println("ExampleA");

} catchException e{

System.out.println("Exception");

}

请问执⾏此段代码的输出是什么?

答:

输出:ExampleA。(根据⾥⽒代换原则[能使⽤⽗类型的地⽅⼀定能使⽤⼦类型],抓取 ExampleA 类型异常的 catch 块能够抓住 try 块

中抛出的 ExampleB 类型的异常)

⾯试题 - 说出下⾯代码的运⾏结果。(此题的出处是《Java 编程思想》⼀书)

class Annoyance extends Exception {

}

class Sneeze extends Annoyance {

}

class Human {

public static void main(String[] args)

throws Exception {

try {

try {

throw new Sneeze();

} catch ( Annoyance a ) {

System.out.println("Caught Annoyance");

throw a;

}

} catch ( Sneeze s ) {

System.out.println("Caught Sneeze");

return ;

} finally {

System.out.println("Hello World!");

}

}

}

结果

Caught Annoyance

Caught Sneeze

Hello World!

10. 常见的 RuntimeException 有哪些?

ClassCastException(类转换异常)

IndexOutOfBoundsException(数组越界)

NullPointerException(空指针)

ArrayStoreException(数据存储异常,操作数组时类型不⼀致)

还有IO操作的BufferOverflowException异常

11. Java常见异常有哪些

lAccessError:违法访问错误。当⼀个应⽤试图访问、修改某个类的域(Field)或者调⽤其⽅法,但是⼜违反域或⽅法的

可见性声明,则抛出该异常。

tiationError:实例化错误。当⼀个应⽤试图通过Java的new操作符构造⼀个抽象类或者接⼝时抛出该异常.

emoryError:内存不⾜错误。当可⽤内存不⾜以让Java虚拟机分配给⼀个对象时抛出该错误。

verflowError:堆栈溢出错误。当⼀个应⽤递归调⽤的层次太深⽽导致堆栈溢出或者陷⼊死循环时抛出该错误。

astException:类造型异常。假设有类A和B(A不是B的⽗类或⼦类),O是A的实例,那么当强制将O构造为类B的实例

时抛出该异常。该异常经常被称为强制类型转换异常。

otFoundException:找不到类异常。当应⽤试图根据字符串形式的类名构造类,⽽在遍历CLASSPAH之后找不到对应

名称的class⽂件时,抛出该异常。

eticException:算术条件异常。譬如:整数除零等。

ndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或⼤于等于数组⼤⼩时抛出。

utOfBoundsException:索引越界异常。当访问某个序列的索引值⼩于0或⼤于等于序列⼤⼩时,抛出该异常。

tiationException:实例化异常。当试图通过newInstance()⽅法创建某个类的实例,⽽该类是⼀个抽象类或接⼝时,抛出

该异常。

FieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。

MethodException:⽅法不存在异常。当访问某个类的不存在的⽅法时抛出该异常。

interException:空指针异常。当应⽤试图在要求使⽤对象的地⽅使⽤了null时,抛出该异常。譬如:调⽤null对象的实例

⽅法、访问null对象的属性、计算null对象的长度、使⽤throw语句抛出null等等。

FormatException:数字格式异常。当试图将⼀个String转换为指定的数字类型,⽽该字符串确不满⾜数字类型要求的

格式时,抛出该异常。

IndexOutOfBoundsException:字符串索引越界异常。当使⽤索引值访问某个字符串中的字符,⽽该索引值⼩于0或⼤

于等于序列⼤⼩时,抛出该异常。

Java异常处理最佳实践

在 Java 中处理异常并不是⼀个简单的事情。不仅仅初学者很难理解,即使⼀些有经验的开发者也需要花费很多时间来思考如何处理异常,

包括需要处理哪些异常,怎样处理等等。这也是绝⼤多数开发团队都会制定⼀些规则来规范进⾏异常处理的原因。⽽团队之间的这些规范往

往是截然不同的。

本⽂给出⼏个被很多团队使⽤的异常处理最佳实践。

1. finally 块中清理资源或者使⽤ try-with-resource 语句

当使⽤类似InputStream这种需要使⽤后关闭的资源时,⼀个常见的错误就是在try块的最后关闭资源。

public void doNotCloseResourceInTry() {

FileInputStream inputStream = null;

try {

File file = new File("./");

inputStream = new FileInputStream(file);

// use the inputStream to read a file

// do NOT do this

inputStream.close();

} catch (FileNotFoundException e) {

log.error(e);

} catch (IOException e) {

log.error(e);

}

}

问题就是,只有没有异常抛出的时候,这段代码才可以正常⼯作。try 代码块内代码会正常执⾏,并且资源可以正常关闭。但是,使⽤ try

代码块是有原因的,⼀般调⽤⼀个或多个可能抛出异常的⽅法,⽽且,你⾃⼰也可能会抛出⼀个异常,这意味着代码可能不会执⾏到 try 代

码块的最后部分。结果就是,你并没有关闭资源。

所以,你应该把清理⼯作的代码放到 finally ⾥去,或者使⽤ try-with-resource 特性。

1.1 使⽤ finally 代码块

与前⾯⼏⾏ try 代码块不同,finally 代码块总是会被执⾏。不管 try 代码块成功执⾏之后还是你在 catch 代码块中处理完异常后都会执

⾏。因此,你可以确保你清理了所有打开的资源。

public void closeResourceInFinally() {

FileInputStream inputStream = null;

try {

File file = new File("./");

inputStream = new FileInputStream(file);

// use the inputStream to read a file

} catch (FileNotFoundException e) {

log.error(e);

} finally {

if (inputStream != null) {

try {

inputStream.close();

} catch (IOException e) {

log.error(e);

}

}

}

}

1.2 Java 7 try-with-resource 语法

如果你的资源实现了 AutoCloseable 接⼝,你可以使⽤这个语法。⼤多数的 Java 标准资源都继承了这个接⼝。当你在 try ⼦句中打开资

源,资源会在 try 代码块执⾏后或异常处理后⾃动关闭。

public void automaticallyCloseResource() {

File file = new File("./");

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);

}

}

2. 优先明确的异常

你抛出的异常越明确越好,永远记住,你的同事或者⼏个⽉之后的你,将会调⽤你的⽅法并且处理异常。

因此需要保证提供给他们尽可能多的信息。这样你的 API 更容易被理解。你的⽅法的调⽤者能够更好的处理异常并且避免额外的检查。

因此,总是尝试寻找最适合你的异常事件的类,例如,抛出⼀个 NumberFormatException 来替换⼀个 IllegalArgumentException 。避

免抛出⼀个不明确的异常。

public void doNotDoThis() throws Exception {

...

}

public void doThis() throws NumberFormatException {

...

}

3. 对异常进⾏⽂档说明

当在⽅法上声明抛出异常时,也需要进⾏⽂档说明。⽬的是为了给调⽤者提供尽可能多的信息,从⽽可以更好地避免或处理异常。

在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。

public void doSomething(String input) throws MyBusinessException {

...

}

4. 使⽤描述性消息抛出异常

在抛出异常时,需要尽可能精确地描述问题和相关信息,这样⽆论是打印到⽇志中还是在监控⼯具中,都能够更容易被⼈阅读,从⽽可以更

好地定位具体错误信息、错误的严重程度等。

但这⾥并不是说要对错误信息长篇⼤论,因为本来 Exception 的类名就能够反映错误的原因,因此只需要⽤⼀到两句话描述即可。

如果抛出⼀个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。⼀个很好的例⼦是

NumberFormatException 。当你以错误的格式提供 String 时,它将被 类的构造函数抛出。

try {

new Long("xyz");

} catch (NumberFormatException e) {

log.error(e);

}

5. 优先捕获最具体的异常

⼤多数 IDE 都可以帮助你实现这个最佳实践。当你尝试⾸先捕获较不具体的异常时,它们会报告⽆法访问的代码块。

但问题在于,只有匹配异常的第⼀个 catch 块会被执⾏。 因此,如果⾸先捕获 IllegalArgumentException ,则永远不会到达应该处理更

具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的⼦类。

总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。

你可以在下⾯的代码⽚断中看到这样⼀个 try-catch 语句的例⼦。 第⼀个 catch 块处理所有 NumberFormatException 异常,第⼆个处

理所有⾮ NumberFormatException 异常的IllegalArgumentException 异常。

public void catchMostSpecificExceptionFirst() {

try {

doSomething("A message");

} catch (NumberFormatException e) {

log.error(e);

} catch (IllegalArgumentException e) {

log.error(e)

}

}

6. 不要捕获 Throwable

Throwable 是所有异常和错误的超类。你可以在 catch ⼦句中使⽤它,但是你永远不应该这样做!

如果在 catch ⼦句中使⽤ Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应⽤程序处理的严

重问题。 典型的例⼦是 OutOfMemoryError 或者 StackOverflowError 。两者都是由应⽤程序控制之外的情况引起的,⽆法处理。

所以,最好不要捕获 Throwable ,除⾮你确定⾃⼰处于⼀种特殊的情况下能够处理错误。

public void doNotCatchThrowable() {

try {

// do something

} catch (Throwable t) {

// don't do this!

}

}

7. 不要忽略异常

很多时候,开发者很有⾃信不会抛出异常,因此写了⼀个catch块,但是没有做任何处理或者记录⽇志。

public void doNotIgnoreExceptions() {

try {

// do something

} catch (NumberFormatException e) {

// this will never happen

}

}

但现实是经常会出现⽆法预料的异常,或者⽆法确定这⾥的代码未来是不是会改动(删除了阻⽌异常抛出的代码),⽽此时由于异常被捕获,

使得⽆法拿到⾜够的错误信息来定位问题。

合理的做法是⾄少要记录异常的信息。

public void logAnException() {

try {

// do something

} catch (NumberFormatException e) {

log.error("This should never happen: " + e);

}

}

8. 不要记录并抛出异常

这可能是本⽂中最常被忽略的最佳实践。可以发现很多代码甚⾄类库中都会有捕获异常、记录⽇志并再次抛出的逻辑。如下:

try {

new Long("xyz");

} catch (NumberFormatException e) {

log.error(e);

throw e;

}

这个处理逻辑看着是合理的。但这经常会给同⼀个异常输出多条⽇志。如下:

17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"

Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"

at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

at java.lang.Long.parseLong(Long.java:589)

at java.lang.Long.(Long.java:965)

at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)

at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

如上所⽰,后⾯的⽇志也没有附加更有⽤的信息。如果想要提供更加有⽤的信息,那么可以将异常包装为⾃定义异常。

public void wrapException(String input) throws MyBusinessException {

try {

// do something

} catch (NumberFormatException e) {

throw new MyBusinessException("A message that describes the error.", e);

}

}

因此,仅仅当想要处理异常时才去捕获,否则只需要在⽅法签名中声明让调⽤者去处理。

9. 包装异常时不要抛弃原始的异常

捕获标准异常并包装为⾃定义异常是⼀个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。

在你这样做时,请确保将原始异常设置为原因(注:参考下⽅代码 NumberFormatException e 中的原始异常 e )。Exception 类提供了

特殊的构造函数⽅法,它接受⼀个 Throwable 作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事

件变得困难。

public void wrapException(String input) throws MyBusinessException {

try {

// do something

} catch (NumberFormatException e) {

throw new MyBusinessException("A message that describes the error.", e);

}

}

10. 不要使⽤异常控制程序的流程

不应该使⽤异常控制应⽤的执⾏流程,例如,本应该使⽤if语句进⾏条件判断的情况下,你却使⽤异常处理,这是⾮常不好的习惯,会严重

影响应⽤的性能。

11. 使⽤标准异常

如果使⽤内建的异常可以解决问题,就不要定义⾃⼰的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中⾸先尽可能使⽤

Java API 提供的异常,如果标准的异常不能满⾜你的要求,这时候创建⾃⼰的定制异常。尽可能得使⽤标准异常有利于新加⼊的开发者看

懂项⽬代码。

12. 异常会影响性能

异常处理的性能成本⾮常⾼,每个 Java 程序员在开发时都应牢记这句话。创建⼀个异常⾮常慢,抛出⼀个异常⼜会消耗1~5ms,当⼀个异

常在应⽤的多个层级之间传递时,会拖累整个应⽤的性能。

仅在异常情况下使⽤异常;

在可恢复的异常情况下使⽤异常;

尽管使⽤异常有利于 Java 开发,但是在应⽤中最好不要捕获太多的调⽤栈,因为在很多情况下都不需要打印调⽤栈就知道哪⾥出错了。因

此,异常消息应该提供恰到好处的信息。

13. 总结

综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,⽽且⼤部分事情都是为了改善代码的可读性或者 API 的可⽤性。

异常不仅仅是⼀个错误控制机制,也是⼀个通信媒介。因此,为了和同事更好的合作,⼀个团队必须要制定出⼀个最佳实践和规则,只有这

样,团队成员才能理解这些通⽤概念,同时在⼯作中使⽤它。

异常处理-阿⾥巴巴Java开发⼿册

1. 【强制】Java 类库中定义的可以通过预检查⽅式规避的RuntimeException异常不应该通过catch 的⽅式来处理,⽐如:

NullPointerException,IndexOutOfBoundsException等等。 说明:⽆法通过预检查的异常除外,⽐如,在解析字符串形式的数

字时,可能存在数字格式错误,不得不通过catch NumberFormatException来实现。 正例:if (obj != null) {…} 反例:try {

(); } catch (NullPointerException e) {…}

2. 【强制】异常不要⽤来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运⾏中的各种意外情况,且异常的处理效率⽐条件

判断⽅式要低很多。

3. 【强制】catch时请分清稳定代码和⾮稳定代码,稳定代码指的是⽆论如何不会出错的代码。对于⾮稳定代码的catch尽可能进⾏区分

异常类型,再做对应的异常处理。 说明:对⼤段代码进⾏try-catch,使程序⽆法根据不同的异常做出正确的应激反应,也不利于定位

问题,这是⼀种不负责任的表现。 正例:⽤户注册的场景中,如果⽤户输⼊⾮法字符,或⽤户名称已存在,或⽤户输⼊密码过于简

单,在程序上作出分门别类的判断,并提⽰给⽤户。

4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理⽽抛弃之,如果不想处理它,请将该异常抛给它的调⽤者。最外层的业

务使⽤者,必须处理异常,将其转化为⽤户可以理解的内容。

5. 【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,⼀定要注意⼿动回滚事务。

6. 【强制】finally块必须对资源对象、流对象进⾏关闭,有异常也要做try-catch。 说明:如果JDK7及以上,可以使⽤try-with-

resources⽅式。

7. 【强制】不要在finally块中使⽤return。 说明:try块中的return语句执⾏成功后,并不马上返回,⽽是继续执⾏finally块中的语句,

如果此处存在return语句,则在此直接返回,⽆情丢弃掉try块中的返回点。 反例:

private int x = 0;

public int checkReturn() {

try {

// x1

等于,此处不返回

return ++x;

} finally {

// 2

返回的结果是

return ++x;

}

}

8. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的⽗类。 说明:如果预期对⽅抛的是绣球,实际接到的是铅

球,就会产⽣意外情况。

9. 【强制】在调⽤RPC、⼆⽅包、或动态⽣成类的相关⽅法时,捕捉异常必须使⽤Throwable类来进⾏拦截。 说明:通过反射机制来调