龙空技术网

java菜鸟到大佬——全网最全异常机制讲解

一个即将退役的码农 718

前言:

现在你们对“java异常机制谁写的”都比较讲究,兄弟们都需要剖析一些“java异常机制谁写的”的相关文章。那么小编也在网络上汇集了一些关于“java异常机制谁写的””的相关资讯,希望咱们能喜欢,各位老铁们一起来了解一下吧!

一.Java异常的定义

java异常地出现为了识别和响应错误而出现的,它是一种一致性机制。

Java异常的作用

Java异常可以让我们在程序开发时将业务代码和异常处理代码分开,提升了程序代码的优雅性和健壮性。

What:异常类型告诉我们是什么异常。

Where:异常的堆栈跟踪告诉我们具体哪行代码发生了异常。

Why:异常信息告诉我们为什么会发生异常。

二.Java异常架构

2.1 Throwable

如上图所示:Throwable 是所有错误和异常的超类。它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

异常信息分成两大类:Error(错误)和 Exception(异常)

2.2 Error(错误)

Error 类及其子类:此类错误一般是比较严重的系统性底层错误,在应用程序中你是无法处理的。

在JVM体系中,它一般代表 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。

典型的例子:

OutOfMemoryError:内存溢出错误;

StackOverflowError:栈溢出错误

它们带来的后果都是强制终止正在执行的线程。

针对以上这些定义,当此类错误发生时,java代码不会去处理,也不会去自定义实现它的子类。

2.3 Exception(异常)

跟error不同,Exception 异常一般是上层代码的错误,它可以被程序捕获和处理的。

Exception 分为两大类:

RuntimeException:运行时异常。

非运行时异常:编译异常。

2.3.1运行时异常

RuntimeException顾名思义发生在在JVM运行期间,编译期不会去检查它。当你没有在程序中声明(throws)或者捕获(try-catch)它时,程序编译也是可以通过的。

比较典型的运行时异常主要包括下面几类:

NullPointerException:空指针异常

ArrayIndexOutBoundException:数组下标越界异常

ClassCastException:类型转换异常

ArithmeticExecption:算术异常

综上所述,运行时异常是有程序逻辑引起的,程序应该从逻辑角度去尽可能避免这类异常的发生。

2.3.2编译时异常

「定义」: Exception 中除 RuntimeException 及其子类之外的异常。

「特点」: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。「该异常我们必须手动在代码里添加捕获语句来处理该异常」

2.4. 受检异常与非受检异常

Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。

2.4.1 可查的异常(checked exceptions)

正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

2.4.2 不可查的异常(编译器不要求强制处置的异常)

包括运行时异常(RuntimeException与其子类)和错误(Error)

三.Java异常关键字

「try」 – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。

「catch」 – 用于捕获异常。catch用来捕获try语句块中发生的异常。

「finally」 – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。

「throw」 – 用于抛出异常。

「throws」 – 用在方法签名中,用于声明该方法可能抛出的异常。

四.Java异常处理流程当程序在运行的过程中出现了异常,会由JVM自动根据异常的类型实例化一个与之类型匹配的异常类。

2.产生异常对象后会判断当前的语句是否存在异常处理,如果现在没有异常处理,就交给JVM进行默认的异常处理,处理方式:输出异常信息,而后结束程序的调用。

3.如果此时存在异常的捕获操作,那么会先有try语句来捕获产生的异常类实例化对象,再与try'语句的每一个catch进行对比,如果有符合的捕获类型,则使用当前catch的语句来进行异常的处理,如果不匹配,则往下继续匹配其他catch。

4.不管最后异常处理是否能够匹配,都要向后执行,如果此时程序中存在finally语句,就先执行finally中的代码。执行完finally语句后需要根据之前的catch匹配结果来决定如何执行,如果之前已经成功捕获异常,就集继续执行finally之后的代码,如果之前没有成功的捕获异常,九江此异常交给JVM进行默认处理。

整个过程就像方法传递参数一样,只是根据catch后面的参数类型进行匹配。既然对象捕获只是一个异常类对象的传递过程,那么根据java中对象自动向上转型,所以异常类对象都可以向父类对象转型,也证明了所有异常类对象都可以使用Exception来接收。

为什么不选择Throwable?

如果该异常只有Exception类型,如果使用Throwable接受,还会表示可以处理Error的错误,二用户是处理不了Error错误的(jvm处理),所以开发中用户可以处理的异常都要求以Exception为主。

Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。

在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。

4.1 声明异常

在Java中,当前执行的语句必属于某个方法,Java解释器调用main方法执行开始执行程序。若方法中存在检查异常,如果不对其捕获,那必须在方法头中显式声明该异常,以便于告知方法调用者此方法有异常,需要进行处理。 在方法中声明一个异常,方法头中使用关键字throws,后面接上要声明的异常。若声明多个异常,则使用逗号分割。如下所示:

通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 「throws」 关键字声明可能会抛出的异常。

Throws异常抛出的原则:

4.2抛出异常

如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。

throw关键字作用是在方法内部抛出一个Throwable类型的异常。任何Java代码都可以通过throw语句抛出异常。

在业务代码中,如果代码可能会产生某种异常,导致程序错误,你可以new 一个合适的异常对象实例并抛出,如下图所示:

在微服务架构中,在做系统集成时,当某个子系统发生故障时,异常会有很多种类型,我们可以统一处理并向外暴露,不用把系统具体的异常细节暴露出去。

4.3 捕获异常

异常的捕获有四种方式:

try-catchtry-catch-finallytry-finallytry-with-resource

try-catch

你可以catch多个异常,并对不同类型的异常做出不同的处理,同一个 catch 也可以捕获多种类型异常,用 | 隔开

try-catch-finally

常规写法

执行步骤

· 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;

· 当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;

· 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;

Java代码示例

try-finally

try块中如果发生异常,异常代码之后的语句不会执行,直接执行finally中的语句。 try块没有引发异常,则执行完try块然后执行finally语句。

使用场景:用在不需要捕获异常的场景,可以保证资源在使用后被关闭。

典型案例:io流的释放;Lock释放;数据库连接关闭;

Q:finally代码块一定会执行吗?

A: finally代码块在下面这些情况下会停止执行:

如果你在代码中使用System.exit()退出了程序。如果finally语句块中发生了异常。如果程序所在的线程终止和死亡。关闭CPU。

try-with-resource

上面例子中,finally块中的方法也可能会抛出 IOException异常, 这种情况下,我们的业务异常就被覆盖了。

别慌,JAVA 7 为我们提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类。

代码实现

看下Scanner

try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用 getSuppressed 方法来获取。

如何选择异常类型

可以根据下图来选择是捕获异常,声明异常还是抛出异常

异常基础总结

try、catch和finally都不能单独使用,只能是try-catch、try-finally或者try-catch-finally。try语句块监控代码,出现异常就停止执行下面的代码,然后将异常移交给catch语句块来处理。finally语句块中的代码一定会被执行,常用于回收资源 。throws:声明一个异常,告知方法调用者。throw :抛出一个异常,至于该异常被捕获还是继续抛出都与它无关。

Java编程思想一书中,对异常的总结。

在恰当的级别处理问题。(在知道该如何处理的情况下了捕获异常。)解决问题并且重新调用产生异常的方法。进行少许修补,然后绕过异常发生的地方继续执行。用别的数据进行计算,以代替方法预计会返回的值。把当前运行环境下能做的事尽量做完,然后把相同的异常重抛到更高层。把当前运行环境下能做的事尽量做完,然后把不同的异常抛到更高层。终止程序。进行简化(如果你的异常模式使问题变得太复杂,那么用起来会非常痛苦)。让类库和程序更安全。五.常见异常处理方式

直接抛出异常

通常在方法内我们无法处理的异常,或者需要使用者关注的异常,都需要往外传递。即在方法上使用「throws」 关键字声明抛出的异常

封装异常再抛出

有时候为了业务,我们需要对特定的多种异常做特殊处理。这个时候就需要去将原生异常catch并封装成业务异常抛出。

自定义异常

习惯上,建立一个RuntimeException的子类,并设置两个构造器,一个无参,一个有参,Throwable 的 toString 方法会打印这些详细信息,调试时很有用。

六.Java异常常见面试题

6.1. Error 和 Exception 区别是什么?

①.Exception(异常)是应用程序中可能的可预测、可恢复问题。一般大多数异常表示中度到轻度的问题。异常一般是在特定环境下产生的,通常出现在代码的特定方法和操作中。在 EchoInput 类中,当试图调用 readLine 方法时,可能出现 IOException 异常。

Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

②.Error(错误)表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。

检查异常 和 未检查异常 的划分

6.2. 运行时异常和一般异常(受检异常)区别是什么?

Java提供了两类主要的异常:runtime exception和checked exception。checked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。

但是另外一种异常:runtime exception,也称运行时异常,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。

出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类,也有一般异常的特点,是可以被Catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。

如果不想终止,则必须扑捉所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。在这个场景这样处理可能是一个比较好的应用,但并不代表在所有的场景你都应该如此。如果在其它场景,遇到了一些错误,如果退出程序比较好,这时你就可以不太理会运行时异常,或者是通过对异常的处理显式的控制程序退出。

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

在编译生成的字节码中,每个方法都附带一个异常表。异常表中的每一个条目代表一个异常处理器,并且由from指针,to指针,target指针以及所捕获的异常类型构成。这些指针的值是字节码索引,用以定位字节码。

其中,from指针和to指针标识了该异常处理器所监控的范围,例如try代码块所覆盖的范围。target指针则指向异常处理器的起始位置,例如catch代码块的起始位置。

当程序触发异常时,Java虚拟机会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某个异常表条目的监控范围,Java虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,Java虚拟机会将控制流转移至该条目target指针指向的字节码。

如果遍历完所有异常表条目,Java虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的Java栈帧,并且在调用者中重复上述操作。在最坏情况下,Java虚拟机需要遍历当前线程Java栈上所有方法的异常表。

针对异常执行路径,Java编译器会生成一个或多个异常表条目,监控整个try-catch代码块,并且捕获所有种类的异常。这些异常表条目的target指针将指向另一份复制的finally代码块。并且,在这个finally代码块的最后,Java编译器会重新抛出所捕获的异常。

可见,一共有三份finally代码块。其中,前两份分别位于try代码块和catch代码块的正常执行路径出口。最后一份则作为异常处理器,监控try代码块以及catch代码块。它将捕获try代码块触发的未被catch代码块捕获的异常,以及catch代码块触发的异常。

有个小问题?如果catch代码块捕获了异常,并且出发了另一个异常,那么finally捕获并且重抛的异常是哪个呢?答案是后者。也就是所原本的异常便会被忽略掉,不易于代码调试。

6.4. throw 和 throws 的区别是什么?

Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。

「throws 关键字和 throw 关键字在使用上的几点区别如下」

1、throws出现在方法函数头;而throw出现在函数体。

2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

好的编程习惯:

1.在写程序时,对可能会出现异常的部分通常要用try{...}catch{...}去捕捉它并对它进行处理;

2.用try{...}catch{...}捕捉了异常之后一定要对在catch{...}中对其进行处理,那怕是最简单的一句输出语句,或栈输入e.printStackTrace();

3.如果是捕捉IO输入输出流中的异常,一定要在try{...}catch{...}后加finally{...}把输入输出流关闭;

4.如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理。

6.5. final、finally、finalize 有什么区别?

final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

6.6. NoClassDefFoundError 和 ClassNotFoundException 区别?

异常类型

ClassNotFoundException

NoClassDefFoundError

继承模型

从java.lang.Exception继承,是一个Exception类型

从java.lang.Error继承,是一个Error类型

触发原因

当动态加载Class的时候找不到类会抛出该异常

程序在编译时可以找到所依赖的类,但是在运行时找不到指定的类文件,运行过程中Class找不到导致抛出该错误

触发主体

一般在执行Class.forName()、ClassLoader.loadClass()或ClassLoader.findSystemClass()的时候抛出

JVM或者ClassLoader实例尝试加载类的时候,找不到类的定义而发生,通常在importnew一个类的时候触发

处理方式

程序可以从Exception中恢复,ClassNotFoundException可由程序捕获和处理

程序无法从错误中恢复,Error是系统错误,用户无法处理

可能原因

要加载的类不存在;类名书写错误

jar包缺失;调用初始化失败的类

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

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

「注意」:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。

6.8. 常见的 RuntimeException 有哪些?

ClassCastException(类转换异常)IndexOutOfBoundsException(数组越界)NullPointerException(空指针)ArrayStoreException(数据存储异常,操作数组时类型不一致)还有IO操作的BufferOverflowException异常

6.9. 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或大于等于序列大小时,抛出该异常。

七.Java异常处理最佳实践

在 Java 中处理异常并不是一个简单的事情,但是大部分程序员又很容易忽略这个点。

其实处理异常不仅仅初学者很难理解,即使象我一样工作了很多年的老码农也需要花费很多时间来思考如何处理业务中的异常,而且在微服务架构中,你在系统的初始化阶段就要想好这个事情,并设计出有规范的易于使用的可扩展的异常框架,这个往往会在团队协作开发的时候起到很大的作用。

本文给出几个被很多团队使用的异常处理最佳实践。

7.1. 使用finally 块或者try-with-resource 语句来进行资源的清理

我们经常会在代码中对文件流进行处理,在处理完成之后需要关闭文件流,当然我知道很多人会忘记。也有很多职场小白会把关闭代码写在try{}块中,比如下面这个代码:

问题来了,如果try代码块中关闭流方法之前发生的异常,这时候直接进入到catch代码块了,关闭流失效,这其实在真实工作中是一个非常严重的问题。

这个时候你应该把清理工作的代码放到 finally 里去,或者使用 try-with-resource 特性。

7.1.1 使用 finally 代码块

与前面几行 try 代码块不同,finally 代码块总是会被执行。不管 try 代码块成功执行之后还是你在 catch 代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。

7.1.2 Java 7 的 try-with-resource 语法

如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。

7.2 尽量使用标准的异常

代码重用是值得提倡的,这是一条通用规则,异常也不例外。

重用现有的异常有几个好处:

它使得你的API更加易于学习和使用,因为它与程序员原来已经熟悉的习惯用法是一致的。对于用到这些API的程序而言,它们的可读性更好,因为它们不会充斥着程序员不熟悉的异常。异常类越少,意味着内存占用越小,并且转载这些类的时间开销也越小。

Java标准异常中有几个是经常被使用的异常。如下表格:

异常

使用场合

IllegalArgumentException

参数的值不合适

IllegalStateException

参数的状态不合适

NullPointerException

在null被禁止的情况下参数值为null

IndexOutOfBoundsException

下标越界

ConcurrentModificationException

在禁止并发修改的情况下,对象检测到并发修改

UnsupportedOperationException

对象不支持客户请求的方法

虽然它们是Java平台库迄今为止最常被重用的异常,但是,在许可的条件下,其它的异常也可以被重用。例如,如果你要实现诸如复数或者矩阵之类的算术对象,那么重用ArithmeticException和NumberFormatException将是非常合适的。如果一个异常满足你的需要,则不要犹豫,使用就可以,不过你一定要确保抛出异常的条件与该异常的文档中描述的条件一致。这种重用必须建立在语义的基础上,而不是名字的基础上。

最后,一定要清楚,选择重用哪一种异常并没有必须遵循的规则。例如,考虑纸牌对象的情形,假设有一个用于发牌操作的方法,它的参数(handSize)是发一手牌的纸牌张数。假设调用者在这个参数中传递的值大于整副牌的剩余张数。那么这种情形既可以被解释为IllegalArgumentException(handSize的值太大),也可以被解释为IllegalStateException(相对客户的请求而言,纸牌对象的纸牌太少)。

7.3异常文档说明必需要有

在文档中对异常进行详细的说明,包括异常的code,message,以及出现的场景,可能引发的问题。

public void doSomething(String input) throws MyBusinessException {

...

}

7.4不要捕获 Throwable 类

我们在前面提过Throwable 是所有异常和错误的超类,如果你在代码中catch了Throwable 类,相当于你不仅捕获了业务异常,同时也捕获了系统性的错误,比如OutOfMemoryError 或者 StackOverflowError。这可能导致本该由JVM抛出的严重问题被屏蔽了,从而导致无法处理。

下面的反例值得正视:

7.5. 不要把异常忽略掉

在实际开发过程中,有些时候,catch了异常,但是呢又不知道如何处理,甚至没有记录任何日志。一旦异常发生,我们无法获取到任何错误信息,阻碍我们排查问题。

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

7.6. 不要使用异常控制程序的流程

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

7.7. 使用标准异常

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

7.8.异常会影响性能

实验

首先,我们看看没有try-catch情况下,进行100万次加法的耗时:

经过5次统计,其平均耗时为:1816048纳秒,即1.8毫秒。

接着,我们来看看在有try-catch情况下,进行100万次加法的耗时:

经过5次统计,其平均耗时为:1928394纳秒,即1.9毫秒。

我们再来看看,如果try-catch抛出异常,进行100万次加法的耗时:

经过5次统计,其平均耗时为:780950471纳秒,即780毫秒。

经过上面三次统计,我们可以看到在没有try catch时,耗时1.8毫秒。在有try catch 但是没有抛出异常,耗时1.9毫秒。在有抛出异常,耗时780毫秒。我们能得出一个结论:如果try catch没有抛出异常,那么其对性能几乎没有影响。但如果抛出异常,那对程序将造成几百倍的性能影响。

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

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

标签: #java异常机制谁写的 #java查找关键字出现的位置不对