文章目录

  • @[TOC]
  • 一.什么是Java中的异常?
  • 二.Java中的异常处理关键字
  • 三.异常分类(层次结构)
  • 四.常见的异常
  • 五.Java中异常处理的两种方式
  • 六.异常处理常见面试题
  • 1.解释Java异常层次结构?
  • 2.Java异常类的重要方法是什么?
  • 3.解释Java 7 ARM功能和multi-catch块?
  • 4.Java中Checked和Unchecked Exception有什么区别?
  • 5.Java中throw和throws关键字有什么区别?
  • 6.“主线程中的异常”有哪些不同的情况?
  • 7.Java中的final,finally和finalize有什么区别?
  • 8.当main方法抛出异常时会发生什么?
  • 9.我们可以有一个空的catch块吗?
  • 10.error和exception有什么区别?
  • 11.运行时异常和一般异常有何不同?
  • 12.Java中异常处理机制的原理
  • 13.你平时在项目中是怎样对异常进行处理的。
  • 14.try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
  • 15.try-catch-finally 中哪个部分可以省略?
  • 16. 类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA。
  • 17.在Java中什么是内存不足错误?
  • 18.如何自定义一个异常?
  • 19.Java 中的两种异常类型是什么?他们有什么区别?
  • 20.异常处理的时候,finally 代码块的重要性是什么?
  • 21.异常处理完成以后,Exception 对象会发生什么变化?
  • 七.Java异常处理最佳实践
  • 1. 在 finally 块中清理资源或者使用 try-with-resource 语句
  • 1.1 使用 finally 代码块
  • 1.2 Java 7 的 try-with-resource 语法
  • 2. 优先明确的异常
  • 3. 对异常进行文档说明
  • 4. 使用描述性消息抛出异常
  • 5. 优先捕获最具体的异常
  • 6. 不要捕获 Throwable 类
  • 7. 不要忽略异常
  • 8. 不要记录并抛出异常
  • 9. 包装异常时不要抛弃原始的异常
  • 10. 不要使用异常控制程序的流程
  • 11. 使用标准异常
  • 12. 异常会影响性能
  • 13. 总结
  • 11. 使用标准异常
  • 12. 异常会影响性能
  • 13. 总结

一.什么是Java中的异常?

异常是在程序执行期间可能发生的错误事件,并且会中断它的正常流程。异常可能来自不同类型的情况,例如用户输入的错误数据,硬件故障,网络连接故障等。

每当执行java语句时发生任何错误,都会创建一个异常对象,然后JRE会尝试查找异常处理程序来处理异常。如果找到合适的异常处理程序,则将异常对象传递给处理程序代码以处理异常,称为捕获异常。如果未找到处理程序,则应用程序将异常抛出到运行时环境,JRE将终止该程序。

Java异常处理框架仅用于处理运行时错误,编译时错误不由异常处理框架处理。

二.Java中的异常处理关键字

throw:有时我们明确要创建异常对象然后抛出它来停止程序的正常处理。throw关键字用于向运行时抛出异常来处理它。

throws:当我们在方法中抛出任何已检查的异常而不处理它时,我们需要在方法签名中使用throws关键字让调用者程序知道该方法可能抛出的异常。调用方法可以处理这些异常或使用throws关键字将其传播给它的调用方法。我们可以在throws子句中提供多个异常,也可以与main()方法一起使用。

try-catch:我们在代码中使用try-catch块进行异常处理。try是块的开始,catch是在try块的末尾处理异常。我们可以使用try有多个catch块,try-catch块也可以嵌套。catch块需要一个应该是Exception类型的参数。

finally:finally块是可选的,只能用于try-catch块。由于异常会暂停执行过程,因此我们可能会打开一些不会关闭的资源,因此我们可以使用finally块。finally块总是被执行,无论是否发生异常。

三.异常分类(层次结构)

java异常处理常见问题 java异常处理总结_java

四.常见的异常

java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。

java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.

java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。

java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。

java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。

java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。

java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。

java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。

java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。

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

java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。

**java.lang.NullPointerException:**空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。

java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。

java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。

FileNotFoundException:找不到指定文件,文件路径错误或文件不存在,可能用了绝对路径检查文件是否存在,路径是否写错,多用相对路径。

NoClassDefFoundError:找不到相应的类错误,缺乏当前引用类的jar或jar版本不对->找到jar并放入classpath中或找到合适的版本。

ConcurrentModificationException:并发修改异常,在集合迭代时修改里面的元素->在迭代时不要修改集合或用并发集合做遍历(如:ConcurrentHashMap)。

NoSuchMethodError:类里找不到相应的方法,一般是jar版本不对,当前引用的jar版本中没有这个方法->检查jar版本是否正确。

UnsupportedClassVersionError:版本不支持错误,编译class的jdk和运行时候的jdk版本不一致或比较高->将低版本换成高版本。

StackOverflowError:栈溢出错误,一般是函数的死循环,或递归调用无法退出->检查死循环的代码,或让递归有退出值,或加大栈初始化参数。

五.Java中异常处理的两种方式

(1)通过try catch捕捉异常,顺序上先处理子类异常后父类,非特殊情况catch中不能为空,要处理错误,避免使用catch all(不能很准确的定位错误),尽量用catch,jdk7中可以处理并列异常,这个方法虽然简洁,但是也不够好(不同异常处理方法是一致的;多个异常间必须是平级关系)。清理数据必须放到finally里面,无论是否抛异常,或try中有return语句,finally里面的代码一定会执行,除非碰到System.exit(0)(艾克色特)。

(2)通过throws抛出异常,子类方法中抛出的异常应该是与父类方法中相同,或是父类异常的子类;一定要在main方法里面处理异常,不然jvm可能就退出了。

六.异常处理常见面试题

1.解释Java异常层次结构?

Java异常是分层的,继承用于对不同类型的异常进行分类。Throwable是Java Exceptions Hierarchy的父类,它有两个子对象 - Error和Exception。异常进一步分为检查异常和运行时异常。

错误是超出应用程序范围的特殊情况,并且无法预测并从中恢复,例如硬件故障,JVM崩溃或内存不足错误。

Checked Exceptions是我们可以在程序中预期并尝试从中恢复的特殊情况,例如FileNotFoundException。我们应该捕获此异常并向用户提供有用的消息并正确记录以进行调试。Exception是所有Checked Exceptions的父类。

运行时异常是由错误的编程引起的,例如尝试从Array中检索元素。我们应该在尝试检索元素之前先检查数组的长度,否则它可能会ArrayIndexOutOfBoundException在运行时抛出。RuntimeException是所有运行时异常的父类。

2.Java异常类的重要方法是什么?

异常及其所有子类不提供任何特定方法,并且所有方法都在基类Throwable中定义。

String getMessage() - 此方法返回消息String of Throwable,并且可以在通过构造函数创建异常时提供消息。

String getLocalizedMessage() - 提供此方法,以便子类可以覆盖它以向调用程序提供特定于语言环境的消息。此方法getMessage()的可抛出类实现只是使用方法来返回异常消息。

synchronized Throwable getCause() - 此方法返回异常的原因或null id,原因未知。

String toString() - 此方法以String格式返回有关Throwable的信息,返回的String包含Throwable类和本地化消息的名称。

void printStackTrace() - 此方法将堆栈跟踪信息打印到标准错误流,此方法已重载,我们可以将PrintStream或PrintWriter作为参数传递,以将堆栈跟踪信息写入文件或流。

3.解释Java 7 ARM功能和multi-catch块?

如果你在一个try块中捕获了很多异常,你会发现catch块代码看起来非常难看,并且主要由冗余代码组成,以记录错误,记住Java 7的一个特性是multi-catch块。我们可以在一个catch块中捕获多个异常。具有此功能的catch块如下所示:

catch(IOException | SQLException | Exception ex){

logger.error(ex);

throw new MyException(ex.getMessage());

}

大多数情况下,我们使用finally块来关闭资源,有时我们忘记关闭它们并在资源耗尽时获得运行时异常。这些异常很难调试,我们可能需要查看我们使用该类资源的每个地方,以确保我们关闭它。所以java 7的改进之一是try-with-resources,我们可以在try语句中创建一个资源并在try-catch块中使用它。当执行来自try-catch块时,运行时环境会自动关闭这些资源。具有这种改进的try-catch块样本是:

try (MyResource mr = new MyResource()) {

System.out.println("MyResource created in try-with-resources");

} catch (Exception e) {

e.printStackTrace();

}

4.Java中Checked和Unchecked Exception有什么区别?

Checked Exceptions应该使用try-catch块在代码中处理,否则方法应该使用throws关键字让调用者知道可能从方法抛出的已检查异常。未经检查的异常不需要在程序中处理或在方法的throws子句中提及它们。

Exception是所有已检查异常RuntimeException的超类,而是所有未经检查的异常的超类。请注意,RuntimeException是Exception的子类。

已检查的异常是需要在代码中处理的错误方案,否则您将收到编译时错误。例如,如果您使用FileReader读取文件,它会抛出FileNotFoundException,我们必须在try-catch块中捕获它或将其再次抛给调用方法。

未经检查的异常主要是由编程不良引起的,例如在对象引用上调用方法时的NullPointerException,而不确保它不为null。例如,我可以编写一个方法来从字符串中删除所有元音。确保不传递空字符串是调用者的责任。我可能会改变方法来处理这些场景,但理想情况下,调用者应该处理这个问题。

5.Java中throw和throws关键字有什么区别?

throws关键字与方法签名一起用于声明方法可能抛出的异常,而throw关键字用于破坏程序流并将异常对象移交给运行时来处理它。

6.“主线程中的异常”有哪些不同的情况?

一些常见的主线程异常情况是:

主线程java.lang.UnsupportedClassVersionError中的异常:

当您的java类是从另一个JDK版本编译并且您尝试从另一个Java版本运行它时,会出现此异常。

主线程java.lang.NoClassDefFoundError中的异常:

此异常有两种变体。第一个是您提供类全名和.class扩展名的地方。第二种情况是找不到Class。

主线程java.lang.NoSuchMethodError中的异常:

main:当您尝试运行没有main方法的类时会出现此异常。

线程“main”中的异常java.lang.ArithmeticException:

每当从main方法抛出任何异常时,它都会打印异常是控制台。第一部分解释了从main方法抛出异常,第二部分打印异常类名,然后在冒号后打印异常消息。

7.Java中的final,finally和finalize有什么区别?

final和finally是java中的关键字,而finalize是一种方法。

final关键字可以与类变量一起使用,以便它们不能被重新分配,类可以避免按类扩展,并且使用方法来避免子类覆盖。

finally关键字与try-catch块一起使用,以提供始终执行的语句即使出现一些异常,通常最终也会用来关闭资源。

finalize()方法由垃圾收集器在销毁对象之前执行,这是确保关闭所有全局资源的好方法。

在三者之中,最后只涉及到java异常处理。

8.当main方法抛出异常时会发生什么?

当main()方法抛出异常时,Java Runtime终止程序并在系统控制台中打印异常消息和堆栈跟踪。

9.我们可以有一个空的catch块吗?

我们可以有一个空的catch块,但它是最差编程的例子。我们永远不应该有空的catch块,因为如果异常被该块捕获,我们将没有关于异常的信息,并且它将成为调试它的噩梦。应该至少有一个日志记录语句来记录控制台或日志文件中的异常详细信息。

10.error和exception有什么区别?

error表示系统级的错误,是java运行环境内部错误或者硬件问题,不能指望程序来处理这样的问题,除了退出运行外别无选择,它是Java虚拟机抛出的。

exception 表示程序需要捕捉、需要处理的异常,是由与程序设计的不完善而出现的问题,程序必须处理的问题。

11.运行时异常和一般异常有何不同?

Java提供了两类主要的异常:runtimeException和checkedException 。

**一般异常(checkedException)**主要是指IO异常、SQL异常等。对于这种异常,JVM要求我们必须对其进行cathc处理,所以,面对这种异常,不管我们是否愿意,都是要写一大堆的catch块去处理可能出现的异常。

**运行时异常(runtimeException)**我们一般不处理,当出现这类异常的时候程序会由虚拟机接管。比如,我们从来没有去处理过NullPointerException,而且这个异常还是最常见的异常之一。

出现运行时异常的时候,程序会将异常一直向上抛,一直抛到遇到处理代码,如果没有catch块进行处理,到了最上层,如果是多线程就有Thread.run()抛出,如果不是多线程那么就由main.run()抛出。抛出之后,如果是线程,那么该线程也就终止了,如果是主程序,那么该程序也就终止了。

其实运行时异常的也是继承自Exception,也可以用catch块对其处理,只是我们一般不处理罢了,也就是说,如果不对运行时异常进行catch处理,那么结果不是线程退出就是主程序终止。

如果不想终止,那么我们就必须捕获所有可能出现的运行时异常。如果程序中出现了异常数据,但是它不影响下面的程序执行,那么我们就该在catch块里面将异常数据舍弃,然后记录日志。如果,它影响到了下面的程序运行,那么还是程序退出比较好些。

12.Java中异常处理机制的原理

Java通过面向对象的方式对异常进行处理,Java把异常按照不同的类型进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它都是Throwable或其子类的实例。当一个方法出现异常后就会抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并对异常进行处理。Java的异常处理是通过5个关键词来实现的:try catch throw throws finally。

一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws),我们可以通过它的类型来捕捉它,或最后由缺省处理器来处理它(finally)。

try:用来指定一块预防所有异常的程序

catch:紧跟在try后面,用来捕获异常

throw:用来明确的抛出一个异常

throws:用来标明一个成员函数可能抛出的各种异常

finally:确保一段代码无论发生什么异常都会被执行的一段代码。

13.你平时在项目中是怎样对异常进行处理的。

(1)尽量避免出现runtimeException 。例如对于可能出现空指针的代码,带使用对象之前一定要判断一下该对象是否为空,必要的时候对runtimeException也进行try catch处理。

(2)进行try catch处理的时候要在catch代码块中对异常信息进行记录,通过调用异常类的相关方法获取到异常的相关信息,返回到web端,不仅要给用户良好的用户体验,也要能帮助程序员良好的定位异常出现的位置及原因。例如,以前做的一个项目,程序遇到异常页面会显示一个图片告诉用户哪些操作导致程序出现了什么异常,同时图片上有一个按钮用来点击展示异常的详细信息给程序员看的。

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

答:finally中的代码会执行
详解:
执行流程:

  1. 先计算返回值, 并将返回值存储起来, 等待返回
  2. 执行finally代码块
  3. 将之前存储的返回值, 返回出去;
    需注意:
  4. 返回值是在finally运算之前就确定了,并且缓存了,不管finally对该值做任何的改变,返回的值都不会改变。
  5. finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或catch中的值。
  6. 如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出:JVM:System.exit(0);
    代码示例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;这个返回路径就形成了
         * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
         * 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
         */
    } 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;
        //如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
        return a; 
    }
 
}
执行结果:40

15.try-catch-finally 中哪个部分可以省略?

答: catch和finally可以省略其中一个 , catch和finally不能同时省略。
注意:格式上允许省略catch块, 但是发生异常时就不会捕获异常了,我们在开发中也不会这样去写代码.

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 类型的异常)。

面试题 - 说出下面代码的运行结果。(此题的出处是《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!

17.在Java中什么是内存不足错误?

答:在Java中,OutOfMemoryError是 java.lang.VirtualMachineError的一个子类,当堆内存耗尽时会被JVM抛出。我们能通过设置Java选项来提供更大的内存供应用使用来达到修复的目的。

$>java MyProgram -Xms1024m -Xmx1024m -XX:PermSize=64M -XX:MaxPermSize=256m

18.如何自定义一个异常?

答:继承一个异常类,通常是RumtimeException或者Exception

19.Java 中的两种异常类型是什么?他们有什么区别?

Java 中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用 throws 语句在方法或者是构造函数上声明。

20.异常处理的时候,finally 代码块的重要性是什么?

无论是否抛出异常,finally 代码块总是会被执行。就算是没有 catch 语句同时又抛出异常的情况下,finally 代码块仍然会被执行。最后要说的是,finally 代码块主要用来释放资源,比如:I/O 缓冲区,数据库连接。

21.异常处理完成以后,Exception 对象会发生什么变化?

Exception 对象会在下一个垃圾回收过程中被回收掉。

七.Java异常处理最佳实践

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

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

public void doNotCloseResourceInTry() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        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("./tmp.txt");
        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("./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);
    }
}

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 时,它将被 java.lang.Long 类的构造函数抛出。

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 的可用性。

异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。

该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯,会严重影响应用的性能。

11. 使用标准异常

如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。

12. 异常会影响性能

异常处理的性能成本非常高,每个 Java 程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。

  • 仅在异常情况下使用异常;
  • 在可恢复的异常情况下使用异常;

尽管使用异常有利于 Java 开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。

13. 总结

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

异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。

最后:以上内容都是看了很多文章集合在一起的。大家可以学习一下,好好看看。