1. Java的Error与Exception
1.1 总体架构
1.2 错误与异常的区别
Error:
Error
是指在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如jvm自身)处于非正常的、不可恢复的状态。如OutOfMemoryError、StackOverFlowError等。Error
类及其子类的实例,代表了JVM本身的错误,但是这些错误不能被程序员通过代码处理,除了退出运行外别无选择。- 如何可能的话,Error应该在系统级被捕捉,所以程序员应该关注Exception为父类的分支下的各种异常类。
Exception:
Exception以及他的子类
,代表程序运行时发生的各种不期望发生的事件,是程序本身可以处理的异常。- Exception可以被Java异常处理机制处理,是异常处理的核心。
Exception
表示程序需要捕捉、需要处理的异常,是由于程序设计的不完善而出现的问题,程序必须处理的问题。- 异常分为检查型(checked) 、非检查型(unchecked)
- 表示一个由
程序员导致的错误
,是否应该在应用程序级别被处理
- checked异常强制要求程序员为这样的异常做预备处理工作,也就是必须进行try-catch处理,或通过throws语句进行抛出
- 表示一个由
总结: 异常和错误的区别是,异常是可以被处理的,而错误是没法处理的。
unckecked exception:
-
非检查型异常(unckecked exception): Error 和 RuntimeException以及他们的子类,是运行时发生的,无法预先捕捉处理的
javac在编译时,不会提示和发现这样的异常,不要求程序处理这些异常。。
-
如果愿意,我们可以编写代码处理(使用
try…catch…finally
)这样的异常,也可以不处理 -
对于这些异常,我们应该修正代码,而不是去通过异常处理器处理。
-
这样的异常发生的原因多半是代码逻辑有问题,如除0错误
ArithmeticException
,强制类型转换错误ClassCastException
,数组索引越界ArrayIndexOutOfBoundsException
,使用了空对象NullPointerException
等等。
checked exception:
-
检查型异常(ckecked exception): 除了
Error
和RuntimeException
的其它异常,一般是由程序的运行环境导致的。 -
因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如
IOException
,FileNotFoundException
、EOFException
、一些自定义异常
等。javac强制要求程序员为这样的异常做预备处理工作(使用
try…catch…finally
或者throws/throw
)。 -
在方法中要么用
try-catch
语句捕获它并处理,要么用throws子句
声明以抛出它,否则编译不会通过。
总结:
- 异常可以分为检查型异常和非检查型异常,或者分为:运行时异常和非运行时异常,Exception是所有检查异常的父类;
- 检查和非检查是对于javac来说的:
检查型异常必须使用try, catch关键字进行显式的捕获处理,否则编译器会报错。
非检查型异常通常是可以避免的代码逻辑错误,具体可以根据需要来判断是否需要捕获,并不会在编译期强制要求。
1.3 错误与异常的联系
exception
和error
都是继承了throwable
类,在java
中只有throwable
类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。exception
和error
体现了java
平台设计者对不同异常情况的分类:
(1)exception是程序运行中,可以预料的意外情况,并且应该被捕获并进行相应的处理。
(2) error是指在正常情况下,不大可能出现的情况,绝大部分的error都会导致程序(比如jvm自身)处于非正常的、不可恢复的状态。既然是非正常情况,所以不便于、也不需要捕获。
2. Java的Exception处理
2.1 异常处理的几个关键字
Java异常机制用到的几个关键字:try
、catch
、finally
、throw
、throws
。
- try: try用来包裹一段可能会发生任何异常的代码,当try语句块内发生异常时,异常就被抛出。
- catch: catch子句紧跟在try块后面,用来指定想要捕获的异常类型。
(1)在代码中使用try-catch块进行异常处理:try是块的开始,catch是在try块的末尾处理异常。
(2)可以一个try有多个catch块,try-catch块也可以嵌套。 - finally: finally语句块总是会被执行
(1)可以确保一段代码不管发生什么异常状况都要被执行
(2)主要用于回收在try块里打开的资源(如数据库连接
、网络连接
和磁盘文件
)。 - throw : throw语句用来明确地抛出一个异常。
- throws: throws用在方法签名中,用于声明该方法可能抛出的异常。
2.2 异常处理两种方法
-
通过
try...catch...finally
语句块来处理,其中finally语句块可选:try{ ① try块中放可能发生异常的代码。 ② 如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。 ③ 如果发生异常,则尝试去匹配catch块。 }catch(SQLException SQLexception){ ① 每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。 ② catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。 ③ 如果try中没有发生异常,则所有的catch块将被忽略。 }catch(Exception exception){ //... }finally{ ① finally块通常是可选的。 ② 无论异常是否发生,异常是否被匹配处理,finally都会执行。 ③ 一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。 ④ finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。 }
-
在具体位置不处理,而是通过
throws/throw
抛出到上层再进行处理。public static void test() throws MyException{ try { int i = 10 / 0; System.out.println("i=" + i); } catch (ArithmeticException e) { throw new MyException("This is MyException"); } }
2.3 throw和throws关键字
二者的区别:
- 写法上 :
throw
在方法体内使用,throws
函数名后或者参数列表后、方法体前使用。 - 意义 : throw
强调动作
,而throws 表示一种倾向、可能但不一定实际发生
。 - 作用的对象: throws 后面跟的是异常类,可以一个或多个,多个用逗号隔开。throw 后跟的是异常对象,或者异常对象的引用,只能有一个。
使用的注意事项:
- 如果你在方法中抛出了一个异常对象(throw),就必须在方法上声明一个异常的抛出(throws)。
注意: throw的是检查型异常,必须通过throws声明;非检查型异常,无须throws声明
- 如果一个方法调用了抛出异常的方法,那么调用者就必须处理抛出的这个异常(try…catch)。
- 如果一个方法中抛出了异常,那么throw后面的代码不会再被执行。
finally语句中的代码仍然会执行
- 在一种情况下只能抛出一个异常,如果是多个异常的话,就取离它最近的那个异常。 —— 不是很理解 😂
2.4 如何获取异常信息
常用的异常信息获取方法:
-
String getMessage(): 用于获取异常的详细消息字符串。
-
void printStackTrace(): 输出
Throwable
对象的堆栈跟踪信息到控制台。 -
String getLocalizedMessage(): 提供此方法,以便子类可以覆盖它以向调用程序提供特定于语言环境的消息。 —— 从未使用过,其内部实际调用了
getMessage()
-
String toString(): 方法返回String格式的
Throwable
信息,此信息包括Throwable的名字
和本地化信息
( getLocalizedMessage()获取的信息)。public String toString() { String s = getClass().getName(); String message = getLocalizedMessage(); return (message != null) ? (s + ": " + message) : s; }
2.5 用户自定义异常
-
自定义异常类,需要继承
Throwable类
或Exception类
。 -
编写自己的异常类时需要记住下面的几点:
① 所有异常都必须是 Throwable 的子类。
② 如果希望写一个非检查型异常类,则需要继承Exception
类。
③ 如果你想写一个运行时异常类,那么需要继承RuntimeException
类。/* * 自定义的异常类,格式如下,通过super关键字,将错误信息传递给Exception的构造函数中,之后再调用 * toString()方法就可以打出自己想写的异常信息了 */ class MyException extends Exception { MyException(String ErrorMessage) { super(ErrorMessage); } } /* * 自定义异常:java自己的异常可以手动抛出也可以自动抛出,而自己定义的异常java虚拟机不认识 * 所以,我们要通过throw关键字自己抛出异常,抛出异常之后我们有两种处理方式,第一种是throws声明抛出 * 第二种是在下面直接try catch进行处理 */ public class Myyichang { public static void main(String[] args) { try { int c = chu(2, -1); System.out.println(c); } catch(MyException e) { System.out.println(e.toString()); } } static int chu(int a,int b) throws MyException { if(b <= 0) { throw new MyException("出现负数或者零了"); } return a / b; } }
3. 热门面试问题
3.1 final、finally、finalize的区别
final
用于声明变量、方法和类的,分别表示变量值不可变、方法不可重写、类不可以继承finally
是异常处理中的一个关键字,finally{}
语句块中的代码一定会被执行finalize
是Object类的一个方法,在垃圾回收的时候会调用被回收对象的此方法。
3.2 包含异常时,代码的执行逻辑
- 有如下代码,try和finally语句块中均有return语句,finally中的return语句是否会执行,什么时候执行?
String s = "hello"; try { File file = new File("test.txt"); return 0; } catch(Exception e) { e.printStackTrace(); } finally { return 1; }
答案: 会执行,在方法返回调用者前执行,最后返回的是1。
特殊情况: 若在try里面有System.exit(0)
来退出JVM的情况下,finally块中的代码不会执行。
3.3 JDK 7中异常的新特性
JDK7中的两个新特性
- 一是,
一个catch块中可以捕获多个异常
,就像原来用多个catch块一样。 - 二是,
自动化资源管理(ARM)
也称为try-with-resource块
。 - 新特性的作用:都可以在处理异常时减少代码量,同时提高代码的可读性。
multi-catch块
-
允许程序员在一个catch块中捕获多个异常
catch(IOException | SQLException | NullPointerException ex){ logger.error(ex); throw new MyException(ex.getMessage()); }
try-with-resource块
-
大多数情况下,我们使用finally块来关闭资源,有时我们忘记关闭它们并在资源耗尽时获得运行时异常。
-
这些异常很难调试,我们可能需要查看我们使用该类资源的每个地方,以确保我们关闭它。
-
try-with-resources
,允许程序员在try语句中创建一个资源并在try-catch块中使用它。 -
当执行完try-catch块时,运行时环境会自动关闭这些资源。
-
注意: 要求该资源实现了
java.lang.AutoCloseable
或其子类java.io.Closeable
-
代码示例:
try (MyResource mr = new MyResource()) { System.out.println("MyResource created in try-with-resources"); } catch (Exception e) { e.printStackTrace(); }
3.4 异常链
-
常常会在捕获一个异常后抛出另外一个异常,并且希望把原始的异常信息保存下来,这被称为
异常链
。 -
异常链主要作用就是:保存异常信息,在抛出另外一个异常的同时不丢失原来的异常。
static void g() throws ExceptionC { try { f(); } catch (ExceptionB e) { ExceptionC c = new ExceptionC("exception a"); //异常链,使用initCause()来设置cause c.initCause(e); //另外一种实现方式 // throw new Exception("exception a", e); throw c; } } public static void main(String[] args) { try { g(); } catch (ExceptionC e) { e.printStackTrace(); } }
3.5 你平时在项目中是怎样对异常进行处理的?
- 尽量避免出现runtimeException:
(1)对于可能出现空指针的代码,在使用对象之前一定要判断一下该对象是否为空
(2)必要的时候对runtimeException也进行try-catch处理。 - 异常处理要优雅: 进行
try-catch
处理的时候要在catch代码块中对异常信息进行记录,通过调用异常类的相关方法获取到异常的相关信息不仅要给用户良好的用户体验,也要能帮助程序员有效定位异常出现的位置及原因。
- 使用特定异常以便于调试,比如如
IOException
,最好可以具体到时FileNotFoundException
,还是EOFException
。 - 在程序中尽早抛出异常(
Fail-Fast
);在程序后期捕获异常,让调用者处理异常
。 - 资源回收: 使用Java 7
ARM功能
确保资源已关闭或使用finally块
正确关闭它们。 - 使用自定义异常从应用程序API中抛出单一类型的异常。 —— 不是很理解 😂
- 异常的定义要遵循命名约定,始终以Exception结束。
参考链接: