Java基础之异常处理机制

什么是异常

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

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

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

而在程序中,代码在运行中如果出现运行错误,程序会终止运行,这时由于错误导致程序运行终止的情况就是程序出现了异常。异常并不是指语法错误,因为如果语法错了,编译就通不过,不会产生JVM能够识别的字节码文件,是没法运行起来的,所以只有运行中的程序才会有异常一说。

Java 异常体系

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

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

  1. 使用方法的返回值来表示异常情况有局限性,需要无穷列举所有的异常情况;
  2. 异常流程代码和正常流程代码混合一起,代码往往很臃肿,也很复杂性;代码的可读性和可维护性都不高;
  3. 随着系统规模的不断扩大,代码很难维护,特别是系统可拓展性差;

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

// 汽车
class Car {// 汽车是否正常运行public static final boolean IS_OK = true;// 汽车运行public boolean run(int wheelNum) {if (wheelNum == 4) {return true;}return false;}}// 上班族
class Officer {private Car car;public Officer(Car car) {this.car = car;}// 开车去上班public boolean goWorkByCar(int wheelNum) {if (car == null) {// 只能选择其他出行方式了goWorkByOther();return false;}boolean result = car.run(wheelNum);if (result) {System.out.println("升职,加薪,迎娶白富美");return true;}System.out.println("迟到,扣薪,领导天天喷");return false;}// 其他方式上班public void goWorkByOther() {System.out.println("其他方式上班");}}// 运行demo
public class WorkDemo {public static void main(String[] args) {Car car = new Car();Officer officer = new Officer(car);officer.goWorkByCar(4);}}

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

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

  1. 把不同类型的异常情况使用不同的类来表示,不同的异常类有共同的父类;
  2. 分离异常流程代码和正确流程代码;
  3. 规范了异常处理机制,灵活处理异常,能处理就将其捕获并处理,如果处理不了异常,就将其交给调用者来处理;

Java 异常体系:Java API文档中的详细介绍如下

Java 异常体系
  1. Error:表示错误,一般指JVM相关的不可修复的错误,如:系统崩溃、内存溢出、JVM内部错误等,由JVM抛出,我们一般情况下不需要处理,几乎其所有的子类都是以“Error”作为类名后缀;比如:StackOverflowError,当应用程序递归太深而发生内存溢出时,就会抛出该错误。
  2. Exception:表示异常,指程序中出现不正常的情况,异常一般都是需要程序员来处理的(可以捕获或者抛出);几乎其所有的子类都是以“Exception”作为类名的后缀;
  3. Throwable:在Java 体系中,Throwable类是所有错误异常的父类;

当出现了没见过的异常时,可以将异常类的类名拿到Java API文档中去查找,通过文章介绍即可获得异常的详细信息,以及其在Java中的继承、实现体系;常见的Exception有:

  • NullPointerException:空指针异常,一般当对象为null的时候,对该对象做操作时会出现该异常;
  • ArrayIndexOutOfBoundsException:数组的索引越界,操作数组时使用的索引超出了数组的数据范围会出现;
  • NumberFormatException:数字格式化异常,把非数字的数据类型转换为数字类型时使用了非法的转换对象;

Java 的异常详解:

public class ExceptionDemo {public static void main(String[] args) {Integer.valueOf("laofu");}}

运行结果如下:

Java 的异常详解

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

  • throws:当前方法不处理,而是声明抛出,由该方法的调用者来处理;
  • try-catch:在当前方法中使用try-catch的语句块来处理异常;

捕获异常 try-catch

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

public static void main(String[] args) {System.out.println("老夫开始啦");int result = 10 / 2;System.out.println("10 / 2 = " + result);System.out.println("老夫去也");
}

运行结果如下:

老夫开始啦
10 / 2 = 5
老夫去也

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

public static void main(String[] args) {System.out.println("老夫开始啦");int result = 10 / 0;System.out.println("10 / 2 = " + result);System.out.println("老夫去也");
}

运行结果如下:

老夫开始啦
Exception in thread "main" java.lang.ArithmeticException: / by zeroat Main.main(Main.java:5)

通过查看运行结果,运行结果并不是我们想要的,代码中出现了异常,代码被中断运行。对比两次的运行结果,我们可以得出结论:出现异常之后,程序会中断执行,异常必须处理。try-catch 异常捕获:使用try-catch捕获单个异常,语法如下:

try{编写可能会出现异常的代码
} catch(异常类型  e) {处理异常的代码//记录日志/打印异常信息/继续抛出异常
}

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

public class ExceptionDemo {public static void main(String[] args) {System.out.println("老夫开始啦");try {int result = 10 / 0;System.out.println("10 / 2 = " + result);} catch (ArithmeticException e) {System.out.println("异常信息:" + e.getMessage());System.out.println("异常对象:" + e.toString());System.out.println("异常栈追踪:");e.printStackTrace();        }System.out.println("老夫去也");}
}

运行结果如下:

老夫开始啦
异常信息:/ by zero
异常对象:java.lang.ArithmeticException: / by zero
异常栈追踪:
老夫去也
java.lang.ArithmeticException: / by zeroat Main.main(Main.java:6)

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

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

  1. String getMessage():获取异常的详细描述信息;
  2. String toString():获取异常的类型、异常描述信息;
  3. void printStackTrace():打印异常的跟踪栈信息并输出到控制台,但不能在System.out.println()中使用该方法;其中包含了异常的类型、异常的原因、异常出现的位置;在开发和调试阶段,该方法都很有用,方便调试和修改;
try-catch 的底层执行分析

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

try{编写可能会出现异常的代码
}catch(异常类型A  e){  当try中出现A类型异常,就用该catch来捕获.处理异常的代码//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B  e){  当try中出现B类型异常,就用该catch来捕获.处理异常的代码//记录日志/打印异常信息/继续抛出异常
}

注意:

  1. 一个catch语句,只能捕获一种类型的异常,如果需要捕获多种异常类型,就得使用多个catch语句;
  2. try-catch中的代码在只会出现一种类型的异常,只能一个catch捕获,不可能同时匹配多个catch
  3. 在有多个catch语句的代码中出现异常时,会从上到下依次匹配catch语句,所以多个catch语句应该按照从子类到父类的顺序依次定义;
  4. 一旦匹配上其中一个catch之后,便不会匹配剩余的catch,而是会跳出try-catch,执行之后的代码;

捕获多个异常的案例:

捕获多个异常的案例

运行结果如下:

老夫开始啦
NumberFormatException
老夫去也
java.lang.NumberFormatException: For input string: "laofu"at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)at java.lang.Integer.parseInt(Integer.java:580)at java.lang.Integer.parseInt(Integer.java:615)at Main.main(Main.java:7)

抛出异常

抛出异常有两种方式:

  • throw:用于方法内部,用于给调用者返回一个异常对象,和return一样会结束当前方法;
  • throws:运用于方法声明之上,定义于方法参数之后,表示当前方法不处理异常,而是提醒该方法的调用者来处理抛出的异常(一个或者多个异常);如:private static int divide(int num1, int num2) throws Exception {}

throw:运用于方法内部,抛出一个具体的异常对象,中止方法的执行,其语法格式如下:

throw  new  异常类("异常信息");

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

String类的charAt方法

throws:

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

方法重写(Override)中的throws:

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

两小:

  1. 子类方法返回类型和父类方法返回类型相同,或是其子类;
  2. 子类方法不能声明抛出新的异常

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

异常分类

Throwable体系

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

  • runtime异常:又称运行时期异常,此类型的异常在运行时期检查;在编译时期,运行异常并不会检测,就不会出现,只有在运行到相关代码时才会出现;RuntimeException自身及其子类异常都属于runtime异常
  • checked异常:又称编译时期异常,此类型的异常在编译时期就会检查,而且是必须处理的,如果没有处理,就会导致编译失败;除了runtime异常之外的其他异常(包括Exception自身)都属于checked异常
jdk 文档中的异常体系
向下看
异常分类

自定义异常

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

自定义异常类的方式:

  • 受检查的异常:自定义一个受检查的异常类需要继承于java.lang.Exception
  • 运行时异常:自定义一个运行时期检查的异常类,需要继承于java.lang.RuntimeException

一般在开发中,自定义的异常都是运行时异常。

解决开车上班的案例

使用自定义异常解决开车上班案例

异常转译和异常链

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

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

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

异常链案例

Java7的异常新特性

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

异常的抛出更加精确了

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

多异常捕获

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

资源自动关闭

finally代码块

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

finally的两种用法:

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

try...finally 的使用

2. try...catch....finally:程序员自己需要处理异常,最终得手动关闭资源。
需要注意的是:finally不能单独使用

try...catch...finally 的使用

finally不执行的情况:

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

finally 不会执行的情况

finally 和 return

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

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

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

很有趣的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 块中;
  5. 不建议在循环中进行异常处理,应该在循环外对异常进行捕获处理(在循环之外使用try-catch);
  6. 自定义异常尽量使用RuntimeException类型的,并且要尽量避开已存在的异常;

小结

1. 五个关键字:try、catch、finally、throw、throws
2. 异常体系的两个继承结构:

  • Throwable类有两个子类:ErrorException
  • Exception类有一个特殊的子类:RuntimeException
  • RuntimeException类及其子类称之为runtime异常
  • Exception类和子类中除了RuntimeException体系的其他类称之为checked异常

3. 自定义异常类;
4. ErrorException的区别和关系;
5. checked异常runtime异常的区别;
6. finally关键字及其相关知识;
7. finallyreturn的执行顺序;
8. throwthrows的区别;
完结。老夫虽不正经,但老夫一身的才华!

抛出运行时异常的目的_「JAVA」运行时异常、编译时异常、自定义异常,通过案例实践转译和异常链...相关推荐

  1. java 同步解决不安全类_「JAVA」Java 线程不安全分析,同步锁和Lock机制,哪个解决方案更好...

    线程不安全 线程不安全的问题分析:在小朋友抢气球的案例中模拟网络延迟来将问题暴露出来:示例代码如下: public class ImplementsDemo { public static void ...

  2. android string拼接字符串_「JAVA」细述合理创建字符串,分析字符串的底层存储,你不该错过...

    Java基础之字符串操作--String 字符串 什么是字符串?如果直接按照字面意思来理解就是多个字符连接起来组合成的字符序列.为了更好的理解以上的理论,我们先来解释下字符序列,字符序列:把多个字符按 ...

  3. java 中的finally你知多少_「JAVA」详述Java异常体系,处理异常时配上finally效果更佳...

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

  4. docker 容器运行 golang程序_「Docker」 - 运行 amp; 管理容器

    容器是基于容器技术所建立和运行的轻量级应用运行环境,它是Docker封装和管理应用程序或微服务的「集装箱」,在Docker中,容器是最核心的部分. 一.容器的创建和启动 Docker容器的生命周期里分 ...

  5. activemq后台管理 看topic消息_「Java」 - SpringBoot amp; ActiveMQ

    一.消息队列 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合.异步消息.流量削锋等问题,实现高性能.高可用.可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件. 目前在生产环境中使用较 ...

  6. unity应用开发实战案例_「简历」STAR法则的实战应用,附手把手教学案例

    关注@应届生求职网,了解更多求职信息 本文共2072字,预计阅读需3分钟 本期分享导师-Anna_青云导师 51Job职场导师 知乎职场千赞答主 多年猎头及多行业人力资源管理经历 职业生涯规划师.职业 ...

  7. 保存数组类型数据_「Java」基础12:什么叫数组?

    所谓数组,就是有序的元素序列 ,在数学里面都有讲到这个概念. 那么程序中的数组和数学里的数组又有哪些不同呢? 一.数组的定义与访问 举一个现实生活中的例子: 一个500毫升的杯子,既可以拿来装水,也可 ...

  8. android javamail获取邮件太多太慢_「Java」 - SpringBoot amp; 邮件发送

    发送邮件是web应用系统的必备功能之一,用于用户注册验证.忘记密码找回或者发送营销信息.最早期使用JavaMail相关API写发送邮件的相关代码,后来Spring推出了JavaMailSender简化 ...

  9. java接口多态的变量能_「JAVA」多态的灵魂,面向接口的程序设计,这才是你该懂得的接口(interface)...

    Java面向对象之接口--interface 什么是接口 一般计算机中的接口分为硬件接口和软件接口. 硬件接口:是指两个硬件设备之间的连接方式,既包括物理上的接口,还包括逻辑上的数据传送协议. 软件接 ...

最新文章

  1. Android Intent的几种用法全面总结
  2. 发展第三代AI:清华AI研究院基础理论研究中心成立,朱军任主任
  3. MySQL优化—工欲善其事,必先利其器之EXPLAIN
  4. 深度解读谷歌开源的最精确自然语言解析器SyntaxNet
  5. 2021-09-14Apriori 算法是基于关联规则的高效数 据挖掘算法
  6. tf2 --- 结构化数据建模流程
  7. 周志明:终于薅住了这位 “社恐”作者的小辫子
  8. unbuntu 安装虚拟环境 virtualenv和virtualenvwrapper
  9. 印章、拿金币、数字游戏
  10. 3个月备战政治数学,你成功上岸了么?
  11. python 数据挖掘_Python数据挖掘框架scikit数据集之iris
  12. 自动化测试的定位以及一些思考是什么样的,你知道吗?
  13. dpdk发送RST报文(一)—— 构建RST包
  14. A律十三折线法G711编解码介绍
  15. 数据可视化之汽车销量,截止到2022年目前中国汽车保有量是3.02亿辆
  16. 产品升级|1-2月合刊:多款重磅产品来袭!
  17. FPGA 之 SOPC 系列(二)SOPC开发流程及开发平台简介
  18. 电脑键盘如何计算机,如何认定一个好的电脑键盘?
  19. B2:Unity制作Moba类游戏——聊天系统
  20. 修改git提交commit信息NAME和EMAIL

热门文章

  1. python3 类字符串名实例化对象
  2. FreeLibraryAndExitThread DLL中线程的安全退出
  3. _splitpath,_makepath分析路径
  4. _declspec(naked) 使用(裸函数)
  5. SetTimer的使用问题
  6. 在ListCtrl控件中插入图标
  7. 如何解决http封包中gzip编码的html
  8. hive 提交不到yarn_在Linux将MapReduce程序提交给YARN执行
  9. 基本概念_程序员基本功——链表的基本概念
  10. 点在平面上的投影坐标例题_光测力学-栅线投影(面结构光)技术