简介
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,该怎么处理异常??
Java提供了优秀的解决办法:异常处理机制。Java异常可以在程序中捕获处理,也可以 throw 抛出。
Java异常处理机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。
Java异常架构
1、Throwable
是 Java 语言中所有错误与异常的顶层父类。派生出 Error 类和 Exception 类。
2、Error
(错误)代表了JVM本身的错误。错误不能被程序员通过代码处理。
2.1、VirtualMachineError
:当Java 虚拟机崩溃或用尽了它继续操作所需的资源时,抛出该错误。
2.1.1、StackOverflowError
:当应用程序递归太深而发生堆栈溢出时,抛出该错误。
2.1.2、OutOfMemoryError
:因为内存溢出或没有可用的内存提供给垃圾回收器时,Java 虚拟机无法分配一个对象,这时抛出该异常。
2.2、AWTError
:当发生严重的 Abstract Window Toolkit 错误时,抛出此错误。
3、Exception
(异常)代表程序运行时发生的各种不期望发生的事件。程序本身可以捕获并且可以处理的异常,是异常处理的核心。Exception 这种异常又分为两类:运行时异常和编译时异常。
运行时异常:
3.1、RuntimeException
表示Java 虚拟机在运行期间可能出现的异常的超类。
3.1.1、NullPointerException
:当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
3.1.2、IndexOutOfBoundsException
:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
3.1.3、ClassCastException
:当试图将对象强制转换为不是实例的子类时,抛出该异常。
3.1.4、ArithmeticException
:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
编译时异常:
Exception 中除 RuntimeException 及其子类之外的异常。
3.2、IOException
:当发生某种 I/O 异常时,抛出此异常。
3.2.1、EOFException
:当输入过程中意外到达文件或流的末尾时,抛出此异常。
3.2.3、FileNotFoundException
:当试图打开指定路径名表示的文件失败时,抛出此异常。
3.3、ClassNotFoundException
:当应用程序试图加载类,没有找到类定义时,抛出该异常。
Exception中运行时异常和编译时异常区别?
- 运行时异常
- 运行时异常(RuntimeException)属于非受检异常。一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。当然,如果程序运行时产生异常,需要修改代码逻辑避免改进。比如,做除法前先判断除数是否为零。
- RuntimeException 异常会由 Java 虚拟机自动抛出并捕获,就算我们没写异常捕获语句运行时也会抛出错误!
- 编译时异常
- 编译时异常(除 RuntimeException 及其子类之外的异常)属于受检异常。Java 编译器会检查它。比如 IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
- 在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。
注:Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。
受检异常:编译器要求必须处理的异常。除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常。
非受检异常:编译器不会进行检查并且不要求必须处理的异常。也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。
Java异常处理
异常处理的基本语法
try
监听异常。将要被监听的代码放在 try 语句块之内,当 try 语句块内发生异常时,异常就被抛出。catch
捕获异常。catch 用来捕获 try 语句块中发生的异常。finally
finally语句块总是会被执行。通常用来做资源释放操作:关闭文件,关闭数据库连接等。throw
抛出异常。throws
用于声明该方法可能抛出的异常。
Java异常处理一般分为三步:1.声明异常 2.抛出异常 3.监听捕获异常。
声明异常
如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则 javac 保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{
//foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
}
抛出异常
方法体中,如果觉得解决不了某些异常问题,可根据抛出异常类型确定是否需要调用者处理,那么你可以通过throw 关键字手动显式的抛出一个 Throwable 类型的异常。
throw 语句的后面必须是一个异常对象。throw 语句必须写在方法内部,执行 throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
package 异常;
public class ExceptionDemo {
public static void main(String[] args) {
foo();
System.out.println("main 程序继续");
}
public static void foo() {
try {
int a = 0;
int b = 1/a;
}catch (Exception e) {
System.out.println("运算错误");
throw new ArithmeticException();
}
System.out.println("foo 程序继续");
}
}
输出:
运算错误
Exception in thread "main" java.lang.ArithmeticException
at 异常.ExceptionDemo.foo(ExceptionDemo.java:15)
at 异常.ExceptionDemo.main(ExceptionDemo.java:5)
程序在throw new ArithmeticException()
行抛出异常后退出,不再继续往下运行。
package 异常;
public class ExceptionDemo {
public static void main(String[] args) {
foo();
System.out.println("main 程序继续");
}
public static void foo() {
int a = 0;
int b = 1/a;
System.out.println("foo 程序继续");
}
}
输出:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at 异常.ExceptionDemo.foo(ExceptionDemo.java:11)
at 异常.ExceptionDemo.main(ExceptionDemo.java:5)
如果不加异常语句,JVM会自动抛出并自动捕获,程序在int b = 1/a;
行抛出异常后退出,不再继续往下运行。
注:如果 throw 抛出编译时异常对象,方法的签名上需要使用 throws 相应的异常声明。
监听捕获异常
Java程序中如果不想直接抛出到上一级,自身进行异常处理。那么就需要通过try…catch…finally的形式先进行异常捕获,再根据不同的异常情况来进行相应的处理。
try…catch…finally
1、try 块中的局部变量和 catch 块中的局部变量(包括异常变量),以及 finally 中的局部变量,他们之间不可共享使用。
2、异常匹配是按照 catch 块的顺序从上往下寻找的,只有第一个匹配的 catch 会得到执行。因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,保证每个 catch 块都有存在的意义。
public class ExceptionDemo {
public static void main(String[] args) {
foo();
System.out.println("main 程序继续");
}
public static void foo() {
try {
int a = 0;
int[] arr = { 1, 2, 3 };
int b = 1/a;
System.out.println(arr[5]);
} catch (ArithmeticException e) {
int a = 1;
System.out.println("除数不能为0");
System.out.println(e.getMessage());
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("你访问了不该的访问的索引");
} catch (Exception e) {
int a = 2;
System.out.println("运算错误 a=" + a);
//throw new ArithmeticException();
}finally{
int a = 3;
System.out.println("finally a=" + a);
}
System.out.println("foo 程序继续");
}
}
输出:
除数不能为0
/ by zero
java.lang.ArithmeticException: / by zero
at 异常.ExceptionDemo3.foo(ExceptionDemo3.java:16)
at 异常.ExceptionDemo3.main(ExceptionDemo3.java:7)
finally a=3
foo 程序继续
main 程序继续
注:16行可见,如果捕获异常后不处理(仅打印),程序会继续执行。这样会导致异常 ArithmeticException 信息丢失,不利于跟踪问题。
- public String
getMessage()
:异常的消息字符串。 - public void
printStackTrace()
:获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。把信息输出在控制台。
3、Java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。代码如下:
public class ExceptionDemo2 {
public static void main(String[] args){
try {
foo();
}catch(ArithmeticException ae) {
System.out.println("处理异常");
}
System.out.println("main 继续执行");
}
public static void foo(){
int a = 5/0; //异常抛出点
System.out.println("foo 逻辑执行"); 不会执行
}
}
结果输出:
处理异常
main 继续执行
异常处理后,会接着处理异常后继续处理。
try…finally
执行 try 代码块,不管是否有异常或运行时错误发生,最终都执行finally代码块逻辑。不建议使用,一般使用 try-with-resource 替换。
try-with-resource
finally 语句块一般用于资源释放,JAVA7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要实现AutoCloseable接口,其中包含了单个返回 void 的close()
方法。
public class TryWithResource {
public static void main(String[] args) {
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
class FileInputStream extends InputStream{}
public abstract class InputStream implements Closeable {}
public interface Closeable extends AutoCloseable {}
可以看出,FileInputStream 实现了AutoCloseable
接口。
finally注意项
- finally 块不管异常是否发生,只要对应的 try 执行了,则它一定也执行。只有一种方法让 finally 块不执行:
System.exit()
。因此 finally 块通常用来做资源释放操作:关闭文件,关闭数据库连接等等。 - 在同一 try…catch…finally 块中,try 发生异常,且匹配的 catch 块中处理异常时也抛出异常,那么后面的 finally 也会执行:首先执行finally块,然后去外围调用者中寻找合适的 catch 块。
- finally 中的 return 会覆盖 try 或者 catch 中的返回值。
public class ExceptionDemo2 {
public static void main(String[] args)
{
int result;
result = foo();
System.out.println(result);
result = bar();
System.out.println(result);
result = ret();
System.out.println(result);
}
public static int foo()
{
try{
int a = 5 / 0;
} catch (Exception e){
return 1;
} finally{
return 2;
}
}
public static int bar()
{
try {
return 1;
}finally {
return 2;
}
}
public static int ret()
{
int a = 0;
try {
/*
* return a在程序执行到这一步的时候,这里不是return a而是return 1;这个返回路径就形成了。
* 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=2
* 再次回到以前的返回路径,继续走return 1;
*/
a = 1;
return a;
}finally {
a = 2;
}
}
}
输出:
2
2
1
- finally 中的 return 会抑制(消灭)前面 try 或者 catch 块中的异常。
public class ExceptionDemo2 {
public static void main(String[] args)
{
int result;
try{
result = foo();
System.out.println(result);
} catch (Exception e){
System.out.println(e.getMessage()); //没有捕获到异常
}
try{
result = bar();
System.out.println(result);
} catch (Exception e){
System.out.println(e.getMessage()); //没有捕获到异常
}
}
//catch中的异常被抑制
public static int foo() throws Exception
{
try {
int a = 5/0;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中使用了return");
}finally {
return 100;
}
}
//try中的异常被抑制
public static int bar() throws Exception
{
try {
int a = 5/0;
}finally {
return 100;
}
}
}
输出:
100
100
- finally 中的异常会覆盖(消灭)前面 try 或者 catch 中的异常。
public class ExceptionDemo2 {
public static void main(String[] args)
{
try{
foo();
} catch (Exception e){
System.out.println(e.getMessage()); //输出:我是finaly中的Exception
}
try{
bar();
} catch (Exception e){
System.out.println(e.getMessage()); //输出:我是finaly中的Exception
}
}
//catch中的异常被抑制
public static int foo() throws Exception
{
try {
int a = 5/0;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中抛出了新的异常");
}finally {
throw new Exception("我是finaly中的Exception");
}
}
//try中的异常被抑制
public static int bar() throws Exception
{
try {
int a = 5/0;
}finally {
throw new Exception("我是finaly中的Exception");
}
}
}
输出:
我是finaly中的Exception
我是finaly中的Exception
总结:
- 不要在 fianlly 中使用 return。
- 不要在 finally 中抛出异常。
- 不要在 finally 中做一些其它的事情,finally 块仅仅用来释放资源是最合适的。
类继承中异常
- 父类的方法没有声明异常,子类在重写该方法的时候不能声明异常。
- 子类重写父类方法时,声明的异常不能是父类异常的父类。
- 父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常)。
class A{
public void foo() throws IOException{}
}
class B extends A{
public void foo(){}
}
class C extends A{
// 子类重写父类方法时,声明的异常不能是父类异常的父类。
public void foo()throws Exception{} //编译报错
}
class D extends A{
// IOException是非运行时异常,
public void foo()throws FileNotFoundException,ParseException{} //编译报错
}
异常处理的常见用法
-
直接抛出异常
对于不能处理的异常,在方法签名处使用 throws 关键字声明会抛出的异常。 详见 “声明异常”。 -
自定义异常
继承Exception
类,这样的自定义异常属于检查异常(checked exception)。
继承RuntimeException
,这样的自定义异常属于非检查异常。
自定义的异常应该总是包含如下的构造函数:
public class MyExceptio extends Exception
{
/* 必须实现 */
public MyExceptio()
{
super();
}
/* 必须实现 */
public MyExceptio(String message)
{
super(message);
}
/* 非必需 */
public MyExceptio(String message, Throwable cause)
{
super(message, cause);
}
/* 非必需 */
public MyExceptio(Throwable cause)
{
super(cause);
}
}
- 封装异常再抛出
多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。
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;
}
}
- 捕获异常并处理。
详见 “监听捕获异常”。
Java异常处理常见问题
异常的链化
在一些大型的模块化的软件开发中,一旦一个地方发生异常,将导致一连串的异常。假设 B 模块完成自己的逻辑需要调用 A 模块的方法,如果 A 模块发生异常,则 B 也将不能完成而发生异常,但是 B 在抛出异常时,会将 A 的异常信息掩盖掉,这将使得异常的根源信息丢失。
异常的链化
可以将多个模块的异常串联起来,使得异常信息不会丢失。
异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。
public class ListExceptionDemo {
public static void main(String[] args) throws IOException {
foo1();
System.out.println("main 程序继续");
}
public static void foo1() throws IOException {
try {
foo2();
}catch (IOException ae) {
throw new IOException("foo1 运算错误", ae);
}
System.out.println("foo1 程序继续");
}
public static void foo2() throws IOException {
try {
int a = 0;
int b = 1/a;
}catch (Exception e) {
throw new IOException("foo2 运算错误", e);
}
System.out.println("foo2 程序继续");
}
}
输出:
Exception in thread “main” java.io.IOException: foo1 运算错误
at 异常.ListExceptionDemo.foo1(ListExceptionDemo.java:15)
at 异常.ListExceptionDemo.main(ListExceptionDemo.java:7)
Caused by: java.io.IOException: foo2 运算错误
at 异常.ListExceptionDemo.foo2(ListExceptionDemo.java:25)
at 异常.ListExceptionDemo.foo1(ListExceptionDemo.java:13)
… 1 more
Caused by: java.lang.ArithmeticException: / by zero
at 异常.ListExceptionDemo.foo2(ListExceptionDemo.java:23)
… 2 more
一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈
。
JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器,默认异常处理器打印出异常信息并终止应用程序。
Exception
会打印最终抛出异常的信息。
Caused by
会逐步打印调用抛异常的信息,最终会打印最初的异常点。所以,一般我们只会去看最后的Caused by,寻找问题点。
Java异常处理心得
- 如果明确知道如何处理异常,比如捕获异常后,执行回滚机制,重试机制等,可进行
try...catch
操作。 - 如果不清楚当前异常如何处理,则直接进行
throws
,把异常交给调用层处理。一般不建议把异常try...catch
之后只是仅仅把方法的调用栈打印出来,这样不能叫做真正意义的异常处理。
Java异常处理注意项
- 只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程。
- 优先抛出明确的异常,避免抛出一个不明确的异常。
- 使用描述性消息抛出异常,对异常进行文档说明,建议将所有错误提示信息放在一个配置文件中统一管理。
- 尽量避免检查异常的使用,除非该异常情况的出现很普遍,需要提醒调用者注意处理的话,才使用检查异常;否则使用非检查异常。
- 切忌使用空 catch 块。在捕获了异常之后什么都不做,相当于忽略了这个异常。意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。
- 尽量将异常统一抛给上层调用者,由上层调用者统一进行处理。
- 在 finally 块中清理资源或者使用 try-with-resource 语句
Java异常处理面试题
转自 https://blog.csdn.net/QGhurt/article/details/107953983
结束
https://www.cnblogs.com/dolphin0520/p/3769804.html
https://www.cnblogs.com/lulipro/p/7504267.html
https://blog.csdn.net/QGhurt/article/details/107953983
异常处理-阿里巴巴Java开发手册
1.【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。 说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过catch NumberFormatException来实现。 正例:if (obj != null) {…} 反例:try { obj.method(); } 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块中的返回点。