「JAVA」运行时异常、编译时异常、自定义异常,通过案例实践转译和异常链
什么是异常
从事Java
开发的小伙伴对于“异常”应该不陌生,因为每天都会遇到不少异常,或捕获,或抛出。那究竟什么是异常?异常即非正常的,不同于平常、一般化的情况。
在平时生活中,医生会说你身体的某个部位有异常,该异常会有什么什么的影响,是由某某原因引起的;
再比如:我每天都准时打卡,按时上下班,那么我本月的考勤是正常的,反之,但凡有迟到、旷工、早退的情况之一的,我本月的考勤就会有异常。
而在程序中,代码在运行中如果出现运行错误,程序会终止运行,这时由于错误导致程序运行终止的情况就是程序出现了异常。
异常并不是指语法错误,因为如果语法错了,编译就通不过,不会产生JVM能够识别的字节码文件,是没法运行起来的,所以只有运行中的程序才会有异常一说。
Java 异常体系
异常处理是衡量一门语言是否成熟的标准之一,C
系列的语言诸如:Java
、C++
、C
等都支持异常处理,有自己的一套异常处理机制。异常处理机制可以让程序有更好的容错性,使代码更加健壮。
所以,引入异常处理机制可以解决的问题有:
- 使用方法的返回值来表示异常情况有局限性,需要无穷列举所有的异常情况;
- 异常流程代码和正常流程代码混合一起,代码往往很臃肿,也很复杂性;代码的可读性和可维护性都不高;
- 随着系统规模的不断扩大,代码很难维护,特别是系统可拓展性差;
通过一下这个案例来说明引入异常之前的处理方式:
Car.java:
Officer.java:
WorkDemo.java:
上述案例中,只是简单粗暴地把汽车的状态分为true
和false
两种,细化不够,不能体现出汽车的详细状态;业务逻辑也很局限,如果要拓展,就要花费更大的成本。
针对于上述的问题,Java
基于面向对象的思想提出了解决方案:
- 把不同类型的异常情况使用不同的类来表示,不同的异常类有共同的父类;
- 分离异常流程代码和正确流程代码;
- 规范异常处理机制,灵活处理异常,能处理就将其捕获并处理,如果处理不了异常,就将其交给调用者来处理;
Java 异常体系:
Java API文档中的详细介绍如下:
Error
:表示错误,一般指JVM
相关的不可修复的错误,如:系统崩溃、内存溢出、JVM
内部错误等,由JVM
抛出,一般情况下不需要处理,几乎其所有的子类都是以“Error
”作为类名后缀;比如:StackOverflowError
,当应用程序递归太深而发生内存溢出时,就会抛出该错误。
Exception
:表示异常,指程序中出现不正常的情况,异常一般都是需要程序员来处理的(可以捕获或者抛出);几乎其所有的子类都是以“Exception
”作为类名的后缀;
常见的Exception
有:
1.NullPointerException
:空指针异常,一般当对象为null
的时候,对该对象做操作时会出现该异常;
2.ArrayIndexOutOfBoundsException
: 数组的索引越界,操作数组时使用的索引超出了数组的数据范围会出现;
3.NumberFormatException
:数字格式化异常,把非数字的数据类型转换为数字类型时使用了非法的转换对象;
这里只列出了三种,但Java
中的异常类型远不止这几种,想要了解更多可以去查阅JDK
文档。
Throwable:在Java
体系中,Throwable
类是所有错误和异常的父类;当出现了没见过的异常时,可以将异常类的类名拿到Java API
文档中去查找,通过文章介绍即可获得异常的详细信息,以及其在Java
中的继承、实现体系;
Java 的异常详解:
以下是一段运行会出异常的Java 代码:
运行上述代码案例,运行结果如下:
如果出现异常,会立刻中断运行中的程序,所以必须处理异常,而处理方式有两种:
throws
:当前方法不处理,而是声明抛出,由该方法的调用者来处理;try-catch
:在当前方法中使用try-catch
的语句块来处理异常;
捕获异常 try-catch
出现异常之后,程序会中断执行,所以异常必须处理;处理的方式有两种:捕获和抛出,两种方式二选一即可。先来运行一个案例来证明:
运行结果如下:
通过查看运行结果,是期望的运行结果,代码运行成功;那么接下来对上述案例稍作修改,再来看其运行结果如何:
运行结果如下:
通过查看运行结果,运行结果并不是想要的,代码中出现了异常,代码被中断运行。对比两次的运行结果,可以得出结论:出现异常之后,程序会中断执行,异常必须处理。
try-catch 异常捕获:使用try-catch
捕获单个异常,语法如下:
注意:try和catch都不能单独使用,必须连用。所以可以对上述出现异常的代码使用try-catch来处理:
运行结果如下:
通过查看运行结果,不难发现,使用try-catch
之后,程序遇到异常时不再中断执行,而是跳过异常代码及其之后的在try-catch
中的剩余代码语句,来到catch
代码块中执行定义的异常处理代码;
在上述案例中是打印出了异常信息、异常对象信息、异常栈追踪,其中的3
个方法都是Throwable
类的方法:
-
String getMessage()
:获取异常的详细描述信息; -
String toString()
:获取异常的类型、异常描述信息; -
void printStackTrace()
:打印异常的跟踪栈信息并输出到控制台,但不能在System.out.println()
中使用该方法;其中包含了异常的类型、异常的原因、异常出现的位置;在开发和调试阶段,该方法都很有用,方便调试和修改;
底层的异常处理
而在Java
底层,当代码出现异常时,JVM
会先创建对应的异常类型对象,然后根据异常类型在catch
中进行匹配;
若匹配成功,则会把创建好的异常对象赋值给catch
中声明的异常对象;若匹配失败,则会向上抛出异常。
使用try-catch
捕获多个异常,语法如下:
这里方式其实就是在单个异常捕获的基础上添加了多个异常的匹配,使得异常处理更加精细化。
在使用try-catch
时需要注意:
- 一个
catch
语句,只能捕获一种类型的异常,如果需要捕获多种异常类型,就得使用多个catch
语句; -
try-catch
中的代码在只会出现一种类型的异常,只能一个catch
捕获,不可能同时匹配多个catch
; - 在有多个
catch
语句的代码中出现异常时,会从上到下依次匹配catch
语句,所以多个catch
语句应该按照从子类到父类的顺序依次定义; - 一旦匹配上其中一个
catch
之后,便不会匹配剩余的catch
,而是会跳出try-catch
,执行之后的代码;
捕获多个异常的案例:
运行结果如下:
异常被成功捕获,再没法瞎蹦哒了。
抛出异常
抛出异常有两种方式:
-
throw
:用于方法内部,用于给调用者返回一个异常对象,和return
一样会结束当前方法; -
throws
:运用于方法声明之上,定义于方法参数之后,表示当前方法不处理异常,而是提醒该方法的调用者来处理抛出的异常(一个或者多个异常);如:private static int divide(int num1, int num2) throws Exception {}
throw
语句:运用于方法内部,抛出一个具体的异常对象,中止方法的执行,其语法格式如下:throw new 异常类("异常信息");
一般的,当一个方法出现异常的情况,不知道该方法应该返回什么时,此时就可以返回一个错误,在catch
语句块中使用throw
继续向上抛出异常。return
是返回一个值,throw
是返回一个错误,返回给该方法的调用者。比如:String
类的charAt
方法就是一个很好的例子:
throws
语句:如果每一个方法都放弃处理异常都直接通过throws
声明抛出,最后异常会抛到main
方法,如果此时main
方法还不处理,会继续抛出给JVM
,JVM
底层的处理机制就是打印异常的跟踪栈信息;runtime
异常,默认就是这种处理方式。
方法重写(Override)中的throws:
一同: 方法的签名必须相同。
两小:
1. 子类方法返回类型和父类方法返回类型相同,或是其子类;
2. 子类方法不能声明抛出新的异常;
一大: 子类方法的访问权限必须大于等于父类方法的访问权限。
异常分类
下图是Java
中的异常分类体系,Java
中所有的异常都从Throwable
继承而来,主要分两大类:Error
(错误)和异常(Exception
)。
Throwable体系
异常(Exception
)根据其在编译时期还是运行时期去检查异常可分为:checked异常
和runtime异常
runtime异常
:又称运行时期异常
,此类型的异常在运行时期检查;在编译时期,运行异常并不会检测,就不会出现,只有在运行到相关代码时才会出现;RuntimeException
自身及其子类异常都属于runtime异常
;
checked异常
:又称编译时期异常
,此类型的异常在编译时期就会检查,而且是必须处理的,如果没有处理,就会导致编译失败;除了runtime异常
之外的其他异常(包括Exception自身)都属于checked异常
;
自定义异常
Java
中有着不同的定义好的异常类,分别表示着某一种具体的异常情况,在开发中总是有些异常情况是Java SE
库中没有定义好的,此时就可以根据自己业务的异常情况来定义异常类;把这样的异常类称为自定义异常类。
自定义异常类的方式:
受检查的异常:即checked异常
, 自定义一个受检查的异常类需要继承于java.lang.Exception
;
运行时异常:即runtime异常
, 自定义一个运行时期检查的异常类,需要继承于java.lang.RuntimeException
;一般在开发中,自定义的异常都是运行时异常。
解决开车上班的案例
现在就可以使用自定义异常来解决开车上班的案例中的异常问题:
异常转译和异常链
异常转译:位于最外层的业务系统不需要关心底层的异常细节,通过捕获原始的异常,将其转换为一个新的不同类型的异常,然后再向上抛出;这个过程称为异常转译。
在上述例子中:我的车坏了,在catch
中重新抛出一个新的异常(OfficerException
)给我的调用者(老板),不能把车的异常信息抛给老板看,因为老板不关心这些细节,关心的是我是否迟到。
异常链: 把原始异常包装为新的异常类,形成多个异常的有序排列;异常链由于更加清楚、准确的定位异常出现的位置;在下述案例中,异常一层层抛出,直至异常被处理,在这个过程中,异常链就产生了:
Java7的异常新特性
1.增强的throw
: 对比Java 6
和 Java 7
中对于抛出异常的改进来体现。
2.多异常捕获: 重写捕获多个异常案例来体现。
3.自动资源关闭: 资源类必须直接或者间接实现java.lang.AutoCloseable
接口
finally代码块
finally
语句块表示无论如何(也包括发生异常时)都会最终执行的代码块,比如:当在try
语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等)时,在使用完之后,都得最终关闭打开的资源。
finally的两种用法:
1.try...finally
: 此时没有catch
来捕获异常,因为此时根据应用场景会抛出异常,程序员自己不处理;
2.try...catch....finally
: 程序员自己需要处理异常,最终得手动关闭资源。
需要注意的是:finally
不能单独使用。
finally
不执行的情况:
当只有在try
或者catch
中调用退出JVM
的相关方法,此时finally
才不会执行,否则finally
修饰的代码块永远会执行。比如:System.exit(0);//退出JVM
。
finally
和 return
如果finally
和return
语句同时存在,永远返回finally
中的结果,但是应该避免这种情况的出现。详情看如下的案例:
如果finally
和return
语句同时存在,永远返回finally
中的结果
还有另一种情况也很有趣,一起来看看:
为什么会出现这种情况呢?首先finally肯定是会被执行的,所以a++
之后a
的值变成了14
,但是finally
中没有返回值,值为14
的变量a
并没有被返回;然后接着执行return a
;这里的a
的值在方法执行之初就已经确定了,故返回的值是13
。
处理异常的原则:
1. 异常只能用于非正常情况,try-catch
的存在也会影响性能,尽量缩小try-catch
的代码范围;
2. 需要为异常提供说明文档,可以参考Java doc
,如果自定义了异常或某一个方法抛出了异常,应该在文档注释中详细说明;
3. 尽可能避免异常的出现,如NullPointerException
等;
4. 异常的粒度很重要,应该为一个基本操作定义一个 try-catch
块,切忌将几百行代码放到一个 try-catch
块中;
最后,不要在循环中进行异常处理,应该在循环外对异常进行捕获处理(在循环之外使用try-catch
);自定义异常尽量使用RuntimeException
类型的,并且要尽量避开已存在的异常;
小结
1. 五个关键字:try、catch、finally、throw、throws
;
2. 异常体系的两个继承结构:
Throwable 类有两个子类:Error和Exception。
Exception 类有一个特殊的子类:RuntimeException。
RuntimeException 类及其子类称之为runtime异常。
Exception 类和子类中除了RuntimeException体系的其他类称之为checked异常。
3. 自定义异常类
4. Error
和Exception
的区别和关系
5. checked异常
和runtime异常
的区别
6. finally
关键字及其相关知识
7. finally和return的执行顺序
8. throw
和throws
的区别
完结,老夫虽不正经,但老夫一身的才华!关注我,获取更多编程科技知识。
Java温故知新
ThinkPHP 的join关联查询不使用默认的表前缀
老夫不正经阅读 2.2k
Java8的新特性
codecraft赞 32阅读 27.4k评论 1
一文彻底搞懂加密、数字签名和数字证书!
编程指北赞 71阅读 33.5k评论 20
Java11的新特性
codecraft赞 28阅读 19.3k评论 3
Java5的新特性
codecraft赞 13阅读 21.7k
Java9的新特性
codecraft赞 20阅读 15.3k
Java13的新特性
codecraft赞 17阅读 11.1k
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。