网站首页 > 精选教程 正文
异常
1,异常的定义
首先,从名字上就可以得出,异常,指的就是程序执行过程中发生生的不正常行为。
比如,我们之前经常遇到的算数异常,空指针异常等等。
public class TestDemo220606 {
public static void main(String[] args) {
//System.out.println(10/0);
//Exception in thread "main" java.lang.ArithmeticException: / by zero 除数为0,算数异常
String s1 = null;
s1.length();
//Exception in thread "main" java.lang.NullPointerException //空指针异常
}
}
2,异常的体系
异常的体系结构比较复杂,种类繁多,这里简单的从宏观上给大家总结下:
从图片中,我们可以看出:
1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
2. Error:指的是Java虚拟机无法解决的严重问题,它是错误,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError和OutOfMemoryError。形象的说法就是你的程序得了’‘癌症’‘。
3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。形象的说法就是你的程序得了”小感冒“。
3,异常的分类
从上面的图片也可以看出,异常分为两大类,编译时异常与运行时异常。
1,编译时异常,也称受查异常,在编译的时候发生。
2,运行时异常,也称受查异常,在程序运行的时候发生。
注意:在编写程序的过程中,出现的语法错误不是异常,这个点不要混淆了。
二,异常的处理
1,防御式编程
异常在代码中是经常存在的,所以我们就需要当异常发生时及时的通知程序员,主要方式如下:
1.1,事前防御型(LBLY)
在操作之前就做好充分的检查,如果没有问题才会继续。
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏异常;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配异常;
return;
}
.....
但是这种方法的缺陷在于,我们的正常流程与异常的处理混在了一起,这样就导致代码的结构非常的不清晰。
1.2,事后认错型(EAFP)
先进行操作,遇到问题后再进行处理。
try {
登陆游戏();
开始匹配();
}catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
}
通过我们的EAFP的思想,这样就把整个程序的正常执行流程与异常的处理分开了,使得代码的结构会更加的清晰。其实,我们异常处理的核心思想就是EAFP思想。
2,异常的抛出
当我们在编写程序的时候,如果说出现了异常情况,此时就需要将异常的信息反馈给我们的程序员。在Java里面,我们借助关键字throw,抛出一个指定的异常对象,将错误信息反馈给调用者。
public class TestDemo220608 {
public static void func(int a){
if(a == 0){
throw new RuntimeException("a==0");//new 一个异常对象并抛出,一般情况下我们new的都是一个自定义类型的异常
}
}
public static void main(String[] args) {
int a = 0;
func(a);
}
}
可以看到,如果当a=0传入函数func后,就会抛出一个a ==0的异常,这个时候是我们的程序员手动地抛出的异常。如果出现异常但是我们没有手动抛出的时候,其实是JVM检测到后帮我们抛出的。一般情况下,我们手动抛出的都是我们自己定义的异常类型。
注意:
1,throw必须写在方法体的内部。
2,抛出的必须是Expection或者Expection的子类对象。
3,如果抛出的是运行时异常或者是其子类,那么就可以不用管,交给JVM处理就好。
4,如果你抛出的是一个受查异常,也就是编译时异常,那你就必须要手动地进行异常的声明,不然程序连编译都过不去。
public class TestDemo220608 {
public static void func(int a) throws CloneNotSupportedException {
//throws进行异常的声明
if(a == 0){
throw new CloneNotSupportedException();
}
}
public static void main(String[] args) throws CloneNotSupportedException{
int a = 0;
func(a);
}
}
5,异常一旦抛出,其后面的代码就不会执行了。
public class TestDemo220608 {
public static void func(int a) throws CloneNotSupportedException {
if(a == 0){
throw new CloneNotSupportedException();
}
}
public static void main(String[] args) throws CloneNotSupportedException{
int a = 0;
func(a);
System.out.println("这是异常后的逻辑部分");
}
}
我们可以看到当调用func抛出异常之后,主函数后面的内容就没有再执行了。
总结,其实抛出异常我们一般都是自定义类型的异常,按道理来说其实它的本质不能算是一个异常,只是在逻辑业务要求上它在这个情况下就是一个异常,所以我们就需要把它手动抛出,因为这个时候JVM层面上是不认为它是一个异常的,所以捕获不到,只能是进行手动的抛出。抛出的如果是一个编译时异常,那么我们就需要进行事先的声明,你得先让它通过编译,才能在运行的时候的特定情况下把异常抛出去。
3,异常的捕获
异常的捕获,也就是异常的具体处理方式。主要有两种:异常的声明throws,以及try - catch捕获处理。
1,异常声明throws
处在方法声明的参数列表之后,当方法中抛出编译时异常,此时用户不想处理该异常,此时就可以借助throws声明一下这个异常,然后让方法的调用者去处理。即当前方法不处理异常,提醒方法的调用者处理。
语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
注意:
1,声明的异常必须是Expection或者Expection的子类。
2,方法内部如果抛出了多个编译时异常,那么参数列表后面throws需要声明多个异常,之间用逗号隔开。如果他们之间有父子类关系的话,那么就可以只声明父女类就好。
public class TestDemo220608 {
public static void func(int a) throws Exception {
if(a == 0){
throw new CloneNotSupportedException();
}
if(a == 1){
throw new FileNotFoundException();
}
}
public static void main(String[] args) throws Exception {
int a = 0;
func(a);
}
}
这里要抛出的是两个编译时异常,我们可以在方法的后面一个个进行声明,但是也可以直接声明它们的父类Exception就好。但是这只是一种方法,为了让程序更加的清晰明了,这种方法是不推荐的。
3,方法将编译时异常用throws进行声明之后,那么就需要我们的调用者去进行处理了。如果调用者也不想处理,那就继续用throws进行声明。
2,try - catch
对于throws而言,它其实对异常没有进行真正的处理,而是将其甩给了调用者,然后如果就这样一层层的甩出去的话,最终还是会交给到JVM的手上进行处理。如果想实现自己真正的处理,就需要使用try - catch。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
System.out.println("捕捉到一个空指针异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
这个时候,因为异常捕获到后是我们程序员自己进行处理的,所以整个程序在遇到异常后是不会结束运行的,所以其余的代码逻辑能够进行执行。但像之前,如果最后还是抛给了JVM处理的话,那程序就会直接结束掉。
当然,如果你try - catch捕获到的异常与你catch中定义的不是同一个类型,那照样还是处理不了,会交给JVM。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (ArithmeticException e) {
// 这是对异常进行捕获并实际处理的部分
System.out.println("捕捉到一个算数异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
可以看到,JVM在处理异常后会把程序终止并且输出我们的异常的具体信息,那我们的try - catch同样也是可以把异常信息输出的。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
try - catch可以捕获多个异常,但不是同时捕获,因为不会同时触发多个异常。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}catch (ArithmeticException e1) {
e1.printStackTrace();
System.out.println("捕获到一个算数异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
如果我们要捕获的多个异常之间存在父子类关系,那一定是子类在前,父类在后。因为父类异常类包含所有的子类异常类,所以你把父类写在前面,会把所有有关的异常都会进行捕获,那么后面写的子类的捕获也就没有任何的意义了。
如图,Exception类就包含空指针异常以及算数异常,所以后面的catch没有任何的作用,会报错。
正确的写法只能是:
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
} catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}catch (ArithmeticException e1) {
e1.printStackTrace();
System.out.println("捕获到一个算数异常进行处理!");
}catch (Exception e2) {
e2.printStackTrace();
System.out.println("捕捉到了除算数异常,空指针异常外的其他异常!");
}
System.out.println("这是其余的代码逻辑!");
}
}
把Exception放到最后,能够捕获除开算数异常,空指针异常外的其他异常。当然有的同学可能会说直接用一个catch,里面的异常类型放Exception,这样就可以直接捕获所有的异常。但是这种方法是不好的,因为范围太广了,这样你必须得把异常信息打印一遍你才能知道这是一个什么异常。
同样的,对于一些编译时异常,我们也可以通过try - catch进行捕获处理。
class Person implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo220608 {
public static void main(String[] args) {
try{
Person p1 = new Person();
Person p2 = (Person)p1.clone();
}catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
如果说在之前,那么main函数的参数列表后面是必须要用throws进行这个编译时异常的声明的,因为方法要声明一次,调用者也要声明一次,并且最终会由JVM进行处理,但是现在有了try - catch后,我们可以直接捕获到这个异常并且由我们自己进行处理。
【注意】
try块和之前的throw手动抛异常一样,try块中抛出异常的位置之后的代码是不会执行的。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
System.out.println("hahahaha");//这一句是不会执行的
}
catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");//这一句会执行
}
}
其实这个过程就相当于try中抛出异常被catch捕获然后并处理后,就直接顺着catch往后执行了,所以之前try块中的抛出异常后的部分根本就执行不到。
3,finally
当我们在写代码的时候,有些特定的代码是必须要执行的,不管程序是否发生异常,比如我们打开的资源需要进行回收。但是异常的处理会导致程序执行发生跳转,那么可能有的代码就执行不到,所以就需要使用finally。
public class TestDemo220608 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
try{
// 这是可能会发生异常的程序主体
String s1 = null;
s1.length();
}
catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}finally{
scan.close();
System.out.println("在finally进行资源的关闭!");
}
System.out.println("这是其余的代码逻辑!");
}
}
可以看到,当我们发生异常之后,finally中的内容依然是被执行的,完全点来说,finally中的内容不管你是否发生异常,都会被执行,所以可以很好的用来关闭一些资源。
那看到这,大家可能会有疑问,那既然其余的代码逻辑也能执行,要finally干啥,直接在后面进行操作不是一样的吗?那你看看这段代码:
public class TestDemo220608 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
try{
// 这是可能会发生异常的程序主体
String s1 = null;
s1.length();
}
catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
return;//在这里直接return掉
}finally{
scan.close();
System.out.println("在finally进行资源的关闭!");
}
System.out.println("这是其余的代码逻辑!");
}
}
可以看到,当我们将异常处理好后,直接使用return结束掉程序的执行的话,那你后买你的其余代码逻辑是不是都执行不到了,但是你看即使是这样,finally中的内容还是被执行了,所以,由此可以看出,finally中的内容,无论如何都会执行,用来进行一些必须代码的执行最好不过。
总结,finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果 finally 中也存在 return 语句, 那么就会执行 finally 中的 return,从而不会执行到 try 中原有的 return。(但是在finally中写return不是一个好的习惯)
:white_check_mark:【面试题:】
throw 与 throws 的区别?
throw是直接抛出一个异常,多用于抛出自定义类型的异常,抛出的异常会直接被JVM处理,此时程序也会结束执行。throws是声明一个异常,此时只是让方法本身不用去处理这个异常,而让调用者去处理,如果调用者也不处理继续使用throws最终会被JVM处理,throws可以让程序暂时编译层面上不会报错。
4,异常的处理流程
【调用栈】
关于 “调用栈” 方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述. 在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace() 的方式查看出现异常代码的调用栈。
public class TestDemo220608 {
public static void func(){
String s1 = null;
s1.length();
}
public static void main(String[] args) {
try{
func();
}catch (NullPointerException e) {
e.printStackTrace();
}
}
}
因为是20行func内部s1.length(),这里就是出现了空指针异常,然后main函数中调用了func,所以24行也会显示有错误。根据这一点,我们以后在解决异常的时候,看报的第一行错误就好,一般只要找到报的第一行的异常的位置,然后将其解决掉程序就正常了。
:white_check_mark:总结:异常的处理流程
1,首先是执行try块中的代码,看是否有异常。
2,如果发生异常,终止try块中的代码执行,去catch中进行异常的匹配。
3,如果catch中匹配到了异常类型,那就执行catch中的代码将异常解决。
4,如果catch中没有匹配到异常类型,那就将异常传递给上层调用者。
5,当然对于finally而言,无论是否匹配到,都会执行。
6,如果上层调用者也没有具体的解决措施,那就继续往上层调用者传递。
7,最终如果传递到main函数,而main函数也没有解决措施,那就会直接交给JVM处理,届时程序也会被终止执行。
5,自定义异常类
Java中虽然已经给我们提供了很多的异常类,但是考虑到实际开发过程中的遇到的一些特殊的异常情况,此时需要我们自己去定义这么一个异常类。
class myException extends RuntimeException{
//这是一个自定义异常类
public myException() {
}
public myException(String message) {
super(message);//可能会需要输出异常的具体信息,所以要有一个有参的构造
}
}
public class Test {
public static void isLegal(String s){
if(s.length() > 5){
throw new myException("用户名长度超过限制!");
}else{
System.out.println("登录成功!");
}
}
public static void main(String[] args) {
// 模拟设置用户名中的异常
Scanner scan = new Scanner(System.in);
String s = scan.next();
try{
isLegal(s);
}catch (myException e){
e.printStackTrace();
}finally {
scan.close();
}
}
}
正如上面的代码所展示,我们可以自定义异常类,如果是继承于Exception类,那么你自定义的这个异常就是一个受查异常,如果是继承于RuntimeExpection,那么你自定义的就是一个非受查异常。这个自定义异常类的内部,根据你自己的需求去定义它的构造函数。
- 上一篇: 小白也能看懂的Java异常处理机制
- 下一篇: java小技巧之二进制数组取任意位数转十进制整数
猜你喜欢
- 2024-11-20 小白也能看懂的Java异常处理机制
- 2024-11-20 JVM是如何处理各种异常的呢?
- 2024-11-20 Java异常之异常处理类详解和代码举例
- 2024-11-20 第25天|Java入门有野,异常处理
- 2024-11-20 java安全编码指南之:异常处理
- 2024-11-20 解读Java编程思想--异常处理
- 2024-11-20 Java中异常处理机制的详细解析及其优化示例代码
- 2024-11-20 学习java, 需要知道的异常处理
- 2024-11-20 java异常处理
- 2024-11-20 如何在Java Lambda表达式中处理异常和错误?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)