Java基础语法—— 认识异常
Posted rain67
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础语法—— 认识异常相关的知识,希望对你有一定的参考价值。
文章目录
Java基础语法(十)—— 认识异常
接上篇博客 Java基础语法(九)——String 类
本次内容介绍大纲
一、初识异常
经常有同学看到异常来问了,异常到底是什么? 而在我们之前的学习中,我们其实已经接触到了Java当中的异常。
1.算数异常
首先我们遇到的第一个异常是我们在讲除号运算符时遇到的——算数异常。
见以下代码
我们在分子的位置出现了0,来运行以上代码。
运行结果:
2.数组下标越界异常
在数组篇我们也提到了数组越界的问题
我们来看以下代码:
运行时结果:
查看异常的信息
3.空指针异常
空指针异常也在之前的学习中经常出现
看以下代码
我们将 array 数组置为 null ,之后再去访问这个数组,就出现了空指针异常。
运行结果:
这些异常都是需要我们在平时所积累出来的,
所谓异常指的就是程序在 运行时 出现错误时通知调用者的一种机制.
关键字 “运行时”
有些错误是这样的, 例如将 System.out.println 拼写错了, 写成了 system.out.println. 此时编译过程中就会出错, 这是 “编译期” 出错.
而运行时指的是程序已经编译通过得到 class 文件了, 再由 JVM 执行过程中出现的错误.
4.异常的处理方式
异常的种类有很多, 不同种类的异常具有不同的含义, 也有不同的处理方式.(在此了解即可)
防御式编程
错误在代码中是客观存在的. 因此我们要让程序出现问题的时候及时通知程序猿. 我们有两种主要的方式
LBYL: Look Before You Leap.
在操作之前就做充分的检查.
EAFP: It’s Easier to Ask Forgiveness than Permission.
“事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到问题再处理.
其实很好理解,打一个非常形象的比喻来理解啊:
比如说有一个你非常喜欢的女生,你想要去拉她的手,那么有几种方式呢?
第一种方式 LBYL:问一下:我能拉你的手吗?
这就是操作之前仔细检查。
第二种方式 EAFP:直接先把手拉上,她要是甩开了再说其他的,要是没拒绝就拉着呗。
这就是”事后获取原谅比事前获取许可更容易“. 也就是先操作, 遇到问题再处理.
了解即可,不用特别去记忆。
5.异常的好处
我们看一下,上述的两种风格在处理代码时究竟是怎样的呢?
我们先给一个特定的场景啊,处理王者荣耀游戏开局时的异常代码
LBYL 风格的代码(不使用异常)
代码的每一步执行完都要进行检查,确认正确才能进行下一步。这就是在操作之前做检查。
EAFP 风格的代码
在这里我们能体会到Java 风格的代码异常处理
对比两种不同风格的代码, 我们可以发现, 使用第一种方式, 正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱. 而第二种方式正常流程和错误流程是分离开的, 更容易理解代码.
二、异常的基本语法
接下来,我们就正式开始了 Java当中 处理异常的 基本语法讲解了。
1.基本格式
Java当中 处理异常的基本格式
2. 是否处理异常对程序的影响
我们来看一组代码:
这组代码中,在代码执行的第二步中,我们存在着数组越界异常,那么第三步的 “hello” 是否会打印呢?
我们运行程序,看结果
“hello” 并没有打印,那么这是为什么呢?此时程序出现异常了,而当程序出现异常时,那么代码将不会被执行。
那么我们还是想执行这个“hello”,那么我们该怎么办呢?
我们就将代码写成以下格式的代码:
运行结果;
hello 也成功进行了打印。也就是说,在这种方式下,代码抛出异常,捕获异常之后,代码将继续向后执行。
那么又有同学说了,我们定义的那个 e 没有用到啊,我们再来看 e 的用处
e.printStackTrace — 打印出现异常栈的追踪
我们来看这时程序运行结果
这时就把出现异常的栈的位置打印出来了,这就是 e 的一个作用吧
那么又有同学来问了,为什么之前的 “hello”无法打印出来,之后的try…catch 捕捉异常之后能够打印出来呢?
3.用 try … catch 需要注意的问题
1. 在 catch 块中,一定要捕获相对应的异常,如果程序抛出的异常在catch 块当中,不能被捕获,那么就会交给 JVM 处理。
看以下代码:
在catch 块当中并没有捕获到 数组越界异常,我们来看一下运行结果。
直接交给 JVM 处理,程序终止,不在向下执行。
2.可以通过catch 捕获多个异常
我们可以在 try之后 跟上多个 catch 来捕获异常,如以下代码:
运行程序结果如下:
我们知道,所有的异常都继承于Exception,那么有人问了,我们可以直接捕获一个Exception的异常吗?
我们来试一下,
我们知道捕捉异常的顺序是按照代码书写的顺序执行的,大家看一下这段代码
当Exception 这个父类异常放在开头,那么下面的捕捉异常则进行报错。
如果这样写,那么Exception 后面所有的异常都失效了,为什么呢,因为不管 try {} 里面是什么异常,都会在Exception 这一步进行捕获到,后面的捕获异常自然失效了。
所以当我们在前面写了 Exception 这个捕获异常时,后面就不要在进行捕获其他异常了。
那么又有同学说了,我们为了省事,那么我们以后都捕获 Exception 这个 异常不就好了?
当然不行,在这种情况下,我们不能区分我们捕获到的是什么异常。所以,不建议大家直接捕获到一个 Exception 的异常。
3. 不建议大家直接捕获一个 Exception 的异常
有些同学想要省事,想要将两个异常合并成一条进行捕捉,我们只需要将两个异常用 | 进行连接即可。
给个示例:
看一下运行结果:
成功运行。
总结:
1. 在 catch 块中,一定要捕获相对应的异常,如果程序抛出的异常在catch 块当中,不能被捕获,那么就会交给 JVM 处理。
2. 可以通过catch 捕获多个异常
3. 不建议大家直接捕获一个 Exception 的异常
4. 可以用 | 同时处理两个异常,如上例。
4.关于异常的处理方式
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息.
关于 “调用栈”
方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述. 在 JVM 中有一块内存空间称为 “虚拟机栈” 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.
5.finally 的使用
我们再来看一下异常基础语法的学习
我们看到了try…catch 之后还可以跟着 finally
那我们来说一下 finally 的特性
不管 这个代码 是否抛出异常,finally 的 内容都会被执行。所以 finally 经常来做一些善后的内容。比如:关闭资源
我们来看一下这一组代码
考一下大家,这组代码执行的结果是什么呢?
运行结果如下:
打印结果为2,这是为什么呢?
首先我们需要明确的一点是,finally 的内容一定会被执行。
我们分析一下:
在try 块当中,在打印 array[4]时出现异常,后面的return 语句就不再执行了,所以最后执行 finally 块,返回2.
我们再来看一个代码示例:
这组代码执行的结果是什么呢?
运行结果:
最后返回了2.
这又再次明确了一点:
finally 的内容是一定会被执行的
finally 的使用 总结:
1.finally 块当中的代码终究会被执行的
2 .不建议在 finally 当中出现 return 语句.
6.异常处理流程
好了,到现在,我们算是讲清楚了 try…catch…finally 及异常处理的流程等问题,那么大家以后在写代码的过程中,一定要记得去使用 try…catch,不能一味的交给 JVM 来处理它,好了我们开始下一块内容——抛出异常。
7.抛出异常
除了 Java 内置的类会抛出一些异常之外, 程序猿也可以手动抛出某个异常. 使用 throw 关键字完成这个操作。
throw 一般抛出一个你想要抛出的异常(或者自定义的异常)
(1)throw 的使用
我们来看代码示例:
我们用 throw new 了一个算数异常,为什么要 new 呢? 因为算数异常本身也是一个类,也要实例化。
我们来看运行时结果;
成功的抛出异常了,但是这样写有一个不好的地方。我们抛出了一个异常但是呢,这个异常我们只是抛出了但是并未处理,所以最后程序出现异常后交给JVM处理,程序最后终止。
(2)声明异常
对于我们调用devide 方法的人来说啊,如果 devide 方法的内容很多,我们就看不出 devide 会抛出一个异常。那么为了让调用devide 方法的人知道,我们调用这个方法会抛出这个异常,一般情况下,我们会给这个方法进行声明异常。那么怎么声明呢?
通过throws 声明这个方法会抛出一个异常
下面我们来看代码示例:
方法调用者知道了调用该方法可能会抛出 算数异常,就用上了 try…catch 来捕获异常。
运行结果:
(3)小结
三、Java异常体系
那么异常到底有多少种?要了解这个问题,我们就需要知道Java的异常体系了。
上图并没有将所有的异常都列举出来,只是大概演示一下。
在Java当中,我们所看到的异常,其实也就是对应着一个类。
由上图中我们可以看到,整个Java 异常体系 都是继承于顶层类 Throwable,那么 Throwable 就是所有异常、错误的父类。
对于Throwable 来说,直接继承这个类的有两个子类,
Error ( 错误 ) 和 Exception(异常).
我们来看一下 jdk_api 帮助手册中对 Throwable 的解释
我们这篇讲的是 异常,怎么又出现一个 错误Error呢?
我们也来认识以下Error
比如说我们写一个代码:
运行之后出现以下结果:
我们来对比一下:
异常是以Exception 结尾的,而错误是以 Error 结尾的。
对于Error 来说——这种错误一定得由程序员自己解决。
而对于Exception 来说——异常时可以由程序自己解决的。
而异常又有以下划分
运行时异常 Runtime Exception就是我们上面提到的 算数异常、数组越界异常、类型转换异常等等,那么就有同学问了?
什么是运行时异常?
运行时异常就是在程序运行的时候抛出的异常
什么是编译时异常?
编译时异常就是在程序编译时抛出的异常
如果一段代码可能抛出 受查异常, 那么必须显式进行处理.
显式处理的方式有两种:
a) 使用 try catch 包裹起来
b) 在方法上加上异常说明, 相当于将处理动作交给上级调用者
别忘了 IDEA 神奇的 alt + enter, 能够快速修正代码.
小结:
四、自定义异常类
Java 中虽然已经内置了丰富的异常类, 但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展, 创建符合我们实际情况的异常.
我们来实现一个简单的自定义的异常类
首先 我们要自定义一个异常类 同时继承一个父类异常
那么这个我们自定义的异常怎么用呢?
下面我们来看
运行结果:
这就是我们自定义异常的使用。
我们再来一个代码示例
下面我们给一个真实的业务场景
例如, 我们实现一个用户登陆功能.
此时我们在处理用户名密码错误的时候可能就需要抛出两种异常. 我们可以基于已有的异常类进行扩展(继承), 创建和我们业务相关的异常类.
此时我们的 login 代码可以改成
自定义类的注意事项:
1.自定义异常通常会继承自 Exception 或者 RuntimeException
2.继承自 Exception 的异常默认是受查异常
3.继承自 RuntimeException 的异常默认是非受查异常.
好了今天的知识就分享到这里,希望大家多多练习,熟练掌握,感谢大家的欣赏与关注!!
谢谢欣赏!
完!
以上是关于Java基础语法—— 认识异常的主要内容,如果未能解决你的问题,请参考以下文章