(2020年)JAVA异常介绍和部分异常面试题

A.Java异常简介
Java异常是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。


Java异常架构

(2020年)JAVA异常介绍和部分异常面试题

 

1. Throwable
Throwable 是 Java 语言中所有错误与异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
2. Error(错误)
定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;*Error:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!
3. Exception(异常)
程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
运行时异常
定义:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。
特点:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
编译时异常
定义: Exception 中除 RuntimeException 及其子类之外的异常。
特点: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。
4. 受检异常与非受检异常
Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。
受检异常
编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
非受检异常
编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。


B. Java异常关键字
• try        – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
• catch   – 用于捕获异常。catch用来捕获try语句块中发生的异常。
• finally  – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
• throw   – 用于抛出异常。
• throws – 用在方法签名中,用于声明该方法可能抛出的异常。


C. Java异常处理

(2020年)JAVA异常介绍和部分异常面试题

 

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


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

非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。
一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。

E. 抛出异常
如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。
throw关键字作用是在方法内部抛出一个Throwable类型的异常。任何Java代码都可以通过throw语句抛出异常。


F. 捕获异常
程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。
 

异常部分面试题:

1. try-catch-finally-return执行顺序

  1. 不管是否有异常产生,finally块中代码都会执行
  2. 当try和catch中有return语句时,finally块仍然会执行
  3. finally是在return后面的表达式运算执行的,所以函数返回值在finally执行前确定的,无论finally中的代码怎么样,返回的值都不会改变,仍然是之前return语句中保存的值
  4. finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值

2. 列出常见的几种RunException

  • NullPointerException - 空指针引用异常
  • ClassCastException - 类型强制转换异常
  • IllegalArgumentException - 传递非法参数异常
  • ArithmeticException - 算术运算异常
  • ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
  • IndexOutOfBoundsException - 下标越界异常
  • NegativeArraySizeException - 创建一个大小为负数的数组错误异常
  • NumberFormatException - 数字格式异常
  • SecurityException - 安全异常
  • UnsupportedOperationException - 不支持的操作异常
  • NegativeArrayException - 数组负下标异常
  • EOFException - 文件已结束异常
  • FileNotFoundException - 文件未找到异常
  • SQLException - 操作数据库异常
  • IOException - 输入输出异常
  • NoSuchMethodException - 方法未找到异常
  • java.lang.AbstractMethodError - 抽象方法错误。当应用试图调用抽象方法时抛出。
  • java.lang.AssertionError - 断言错。用来指示一个断言失败的错误。
  • java.lang.ClassCircularityError - 类循环依赖错误。在初始化一个类时,若检测到类之间循环依赖则抛出该异常。
  • java.lang.ClassFormatError - 类格式错误。当Java虚拟机试图从一个文件中读取Java类,而检测到该文件的内容不符合类的有效格式时输出。
  • java.lang.Error - 错误。是所有错误的基类,用于标识严重的程序运行问题。这些问题通常描述一些不应被应用程序捕获的反常情况

3. 请写出你最常见的 5 个 RuntimeException
1)java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
2)java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
3)java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
5)SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。

4. throw 和 throws 的区别
throw:
1)throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
2)throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常。
throws:
1)throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
2)throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
3)throws 表示出现异常的一种可能性,并不一定会发生这种异常。

5. final、finally、finalize 的区别?
1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
2)finally:异常处理语句结构的一部分,表示总是执行。
3)finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法)

 

6. 如果执行finally代码块之前方法返回了结果,或者JVM退出了,finally块中的代码还会执行吗?

不会,只有在try里面是有System.exit(0)来退出JVM的情况下finally块中的代码才不会执行。否则finally块中的代码都会执行。

思路:了解try.cath.finally的执行顺序就会迎刃而解,首先会执行try里面的代码,执行完了会查找有没有finally,如果没有,直接执行return或者是throw,如果有finally,先执行finally里面的代码,再执行try里面的 return或者是throw;一般来讲finally里面不会写return或者是throw,如果写了,会覆盖掉try里面的return和throw。

 

7. error和exception有什么区别?

Error类和Exception类的父类都是throwable类,他们的区别是:

Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try。。。catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。

 

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

try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。

 

9. Java 中什么是异常链?
异常链是指在进行一个异常处理时抛出了另外一个异常,由此产生了一个异常链条,大多用于将受检查异常(checked exception)封装成为非受检查异常(unchecked exception)或者 RuntimeException。特别注意如果你因为一个异常而决定抛出另一个新的异常时一定要包含原有的异常,这样处理程序才可以通过 getCause() 和 initCause() 方法来访问异常最终的根源

 

10. 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作为参数传递,以将堆栈跟踪信息写入文件或流。