java代码块如果出现异常_「JAVA」运行时异常、编译时异常、自定义异常,通过案例实践转译和异常链...

eaa945d6f691adcac07807010f6e1fa7.png

什么是异常

从事Java开发的小伙伴对于“异常”应该不陌生,因为每天都会遇到不少异常,或捕获,或抛出。那究竟什么是异常?异常即非正常的,不同于平常、一般化的情况。

在平时生活中,医生会说你身体的某个部位有异常,该异常会有什么什么的影响,是由某某原因引起的;

再比如:我每天都准时打卡,按时上下班,那么我本月的考勤是正常的,反之,但凡有迟到、旷工、早退的情况之一的,我本月的考勤就会有异常。

而在程序中,代码在运行中如果出现运行错误,程序会终止运行,这时由于错误导致程序运行终止的情况就是程序出现了异常。

异常并不是指语法错误,因为如果语法错了,编译就通不过,不会产生JVM能够识别的字节码文件,是没法运行起来的,所以只有运行中的程序才会有异常一说。

Java 异常体系

异常处理是衡量一门语言是否成熟的标准之一,C系列的语言诸如:Java、C++、C 等都支持异常处理,有自己的一套异常处理机制。异常处理机制可以让程序有更好的容错性,使代码更加健壮。

所以,引入异常处理机制可以解决的问题有:

使用方法的返回值来表示异常情况有局限性,需要无穷列举所有的异常情况;

异常流程代码和正常流程代码混合一起,代码往往很臃肿,也很复杂性;代码的可读性和可维护性都不高;

随着系统规模的不断扩大,代码很难维护,特别是系统可拓展性差;

通过一下这个案例来说明引入异常之前的处理方式:

Car.java:

e569ad17eacdccaf9bc8c5d395240030.png

Officer.java:

3acbb6cbd27f0ec01fa83aa6b2fa689a.png

WorkDemo.java:

4fb4e8fe864867f1b658e13594891f90.png

上述案例中,只是简单粗暴地把汽车的状态分为true和false两种,细化不够,不能体现出汽车的详细状态;业务逻辑也很局限,如果要拓展,就要花费更大的成本。

针对于上述的问题,Java基于面向对象的思想提出了解决方案:

把不同类型的异常情况使用不同的类来表示,不同的异常类有共同的父类;

分离异常流程代码和正确流程代码;

规范异常处理机制,灵活处理异常,能处理就将其捕获并处理,如果处理不了异常,就将其交给调用者来处理;

Java 异常体系:

Java API文档中的详细介绍如下:

0075946267e5d2c0ec1176eb5ee5447e.png

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 代码:

a3e66bbd273cebd5e241af4fa63a5be6.png

运行上述代码案例,运行结果如下:

d3a3876434ca615cee498046cbf6a6a9.png

如果出现异常,会立刻中断运行中的程序,所以必须处理异常,而处理方式有两种:

throws:当前方法不处理,而是声明抛出,由该方法的调用者来处理;

try-catch:在当前方法中使用try-catch的语句块来处理异常;

捕获异常 try-catch

出现异常之后,程序会中断执行,所以异常必须处理;处理的方式有两种:捕获和抛出,两种方式二选一即可。先来运行一个案例来证明:

4f1eec6641a8b0725c8282f578adeb07.png

运行结果如下:

d6b20363a0524ce41b066954ea85db4c.png

通过查看运行结果,是期望的运行结果,代码运行成功;那么接下来对上述案例稍作修改,再来看其运行结果如何:

4651f7146a34dfb2119a9130db019fe7.png

运行结果如下:

a0ec6ce0583f0a8838aa4d8362dba20e.png

通过查看运行结果,运行结果并不是想要的,代码中出现了异常,代码被中断运行。对比两次的运行结果,可以得出结论:出现异常之后,程序会中断执行,异常必须处理。

try-catch 异常捕获:使用try-catch捕获单个异常,语法如下:

ec3f03823157d319a99ea1ef8183d48c.png

注意:try和catch都不能单独使用,必须连用。所以可以对上述出现异常的代码使用try-catch来处理:

95264918108051a539d7108ad89739c7.png

运行结果如下:

dfc620780448ea13e2e97f79ef4b9c71.png

通过查看运行结果,不难发现,使用try-catch之后,程序遇到异常时不再中断执行,而是跳过异常代码及其之后的在try-catch中的剩余代码语句,来到catch代码块中执行定义的异常处理代码;

在上述案例中是打印出了异常信息、异常对象信息、异常栈追踪,其中的3个方法都是Throwable类的方法:

String getMessage():获取异常的详细描述信息;

String toString():获取异常的类型、异常描述信息;

void printStackTrace():打印异常的跟踪栈信息并输出到控制台,但不能在System.out.println()中使用该方法;其中包含了异常的类型、异常的原因、异常出现的位置;在开发和调试阶段,该方法都很有用,方便调试和修改;

底层的异常处理

而在Java 底层,当代码出现异常时,JVM会先创建对应的异常类型对象,然后根据异常类型在catch中进行匹配;

若匹配成功,则会把创建好的异常对象赋值给catch中声明的异常对象;若匹配失败,则会向上抛出异常。

43298bc82e2fad3ea53ca5d30152321f.png

使用try-catch捕获多个异常,语法如下:

5d02829c1f0f9a10269304bd3425096d.png

这里方式其实就是在单个异常捕获的基础上添加了多个异常的匹配,使得异常处理更加精细化。

在使用try-catch 时需要注意:

一个catch语句,只能捕获一种类型的异常,如果需要捕获多种异常类型,就得使用多个catch语句;

try-catch中的代码在只会出现一种类型的异常,只能一个catch捕获,不可能同时匹配多个catch;

在有多个catch语句的代码中出现异常时,会从上到下依次匹配catch语句,所以多个catch语句应该按照从子类到父类的顺序依次定义;

一旦匹配上其中一个catch之后,便不会匹配剩余的catch,而是会跳出try-catch,执行之后的代码;

捕获多个异常的案例:

9fee07407f0d0cffec810d08637de4a4.png

运行结果如下:

251ba12fa03986d8c40c8bb17876e66b.png

异常被成功捕获,再没法瞎蹦哒了。

抛出异常

抛出异常有两种方式:

throw:用于方法内部,用于给调用者返回一个异常对象,和return一样会结束当前方法;

throws:运用于方法声明之上,定义于方法参数之后,表示当前方法不处理异常,而是提醒该方法的调用者来处理抛出的异常(一个或者多个异常);如:private static int divide(int num1, int num2) throws Exception {}

throw语句:运用于方法内部,抛出一个具体的异常对象,中止方法的执行,其语法格式如下:throw new 异常类("异常信息");

一般的,当一个方法出现异常的情况,不知道该方法应该返回什么时,此时就可以返回一个错误,在catch语句块中使用throw继续向上抛出异常。return 是返回一个值,throw 是返回一个错误,返回给该方法的调用者。比如:String类的charAt方法就是一个很好的例子:

bbaf7e917d58e562a238cbfb97483141.png

throws 语句:如果每一个方法都放弃处理异常都直接通过throws声明抛出,最后异常会抛到main方法,如果此时main方法还不处理,会继续抛出给JVM,JVM底层的处理机制就是打印异常的跟踪栈信息;runtime异常,默认就是这种处理方式。

方法重写(Override)中的throws:

一同: 方法的签名必须相同。

两小:

1. 子类方法返回类型和父类方法返回类型相同,或是其子类;

2. 子类方法不能声明抛出新的异常;

一大: 子类方法的访问权限必须大于等于父类方法的访问权限。

异常分类

下图是Java中的异常分类体系,Java 中所有的异常都从Throwable继承而来,主要分两大类:Error (错误)和异常(Exception)。

1b94e937d21d4109ed390a67db7e0005.png

Throwable体系

异常(Exception)根据其在编译时期还是运行时期去检查异常可分为:checked异常和runtime异常

runtime异常:又称运行时期异常,此类型的异常在运行时期检查;在编译时期,运行异常并不会检测,就不会出现,只有在运行到相关代码时才会出现;RuntimeException自身及其子类异常都属于runtime异常;

checked异常:又称编译时期异常,此类型的异常在编译时期就会检查,而且是必须处理的,如果没有处理,就会导致编译失败;除了runtime异常之外的其他异常(包括Exception自身)都属于checked异常;

eab7adc14f83034768c0150372b3bbc1.png

300012d8c9c6577a923d6d72f493b575.png

4fa7caa0fb550778724a09e0cdb44949.png

自定义异常

Java中有着不同的定义好的异常类,分别表示着某一种具体的异常情况,在开发中总是有些异常情况是Java SE库中没有定义好的,此时就可以根据自己业务的异常情况来定义异常类;把这样的异常类称为自定义异常类。

自定义异常类的方式:

受检查的异常:即checked异常, 自定义一个受检查的异常类需要继承于java.lang.Exception;

运行时异常:即runtime异常, 自定义一个运行时期检查的异常类,需要继承于java.lang.RuntimeException;一般在开发中,自定义的异常都是运行时异常。

解决开车上班的案例

现在就可以使用自定义异常来解决开车上班的案例中的异常问题:

45666e0da730fa0826bea80cd8463a5c.png

异常转译和异常链

异常转译:位于最外层的业务系统不需要关心底层的异常细节,通过捕获原始的异常,将其转换为一个新的不同类型的异常,然后再向上抛出;这个过程称为异常转译。

在上述例子中:我的车坏了,在catch中重新抛出一个新的异常(OfficerException)给我的调用者(老板),不能把车的异常信息抛给老板看,因为老板不关心这些细节,关心的是我是否迟到。

异常链: 把原始异常包装为新的异常类,形成多个异常的有序排列;异常链由于更加清楚、准确的定位异常出现的位置;在下述案例中,异常一层层抛出,直至异常被处理,在这个过程中,异常链就产生了:

a9ac3d38f786c20d519faa4642b6eee2.png

Java7的异常新特性

1.增强的throw : 对比Java 6 和 Java 7 中对于抛出异常的改进来体现。

6dc92a05ef839daa0f54c9ec10944ebc.png

2.多异常捕获: 重写捕获多个异常案例来体现。

1b97d7f23555e89212ae0c0f0c833f84.png

3.自动资源关闭: 资源类必须直接或者间接实现java.lang.AutoCloseable接口

a0e3165290606835dd7024c41f6f2397.png

finally代码块

finally语句块表示无论如何(也包括发生异常时)都会最终执行的代码块,比如:当在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等)时,在使用完之后,都得最终关闭打开的资源。

finally的两种用法:

1.try...finally: 此时没有catch来捕获异常,因为此时根据应用场景会抛出异常,程序员自己不处理;

5dea098627bf9ea8ff3c80f2c8ee1823.png

2.try...catch....finally: 程序员自己需要处理异常,最终得手动关闭资源。

需要注意的是:finally不能单独使用。

3ef3e436bedc829eb2e9f74f6183aa22.png

finally不执行的情况:

当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally修饰的代码块永远会执行。比如:System.exit(0);//退出JVM。

244a495eab6a4ac106ad179c67286cdf.png

finally 和 return

如果finally和return语句同时存在,永远返回finally中的结果,但是应该避免这种情况的出现。详情看如下的案例:

2dd8ea391d4c7f4f667f797e9262ef04.png

如果finally和return语句同时存在,永远返回finally中的结果

还有另一种情况也很有趣,一起来看看:

365001d3b961d991a909e2f3c0d1d5e7.png

为什么会出现这种情况呢?首先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的区别

完结,老夫虽不正经,但老夫一身的才华!关注我,获取更多编程科技知识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值