javaexception(Java中的异常)

异常1,异常的定义首先,从名字上就可以得出,异常,指的就是程序执行过程中发生生的不正常行为。 比如,我们之前经常遇到的算数异常,空指针异常等等。public class TestDemo220606 { p...

Java中的异常

异常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,异常的体系异常的体系结构比较复杂,种类繁多,这里简单的从宏观上给大家总结下:Java中的异常

从图片中,我们可以看出:

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); }}Java中的异常

可以看到,如果当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("这是异常后的逻辑部分"); }}Java中的异常

我们可以看到当调用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("这是其余的代码逻辑!"); }}Java中的异常

这个时候,因为异常捕获到后是我们程序员自己进行处理的,所以整个程序在遇到异常后是不会结束运行的,所以其余的代码逻辑能够进行执行。但像之前,如果最后还是抛给了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("这是其余的代码逻辑!"); }}Java中的异常

可以看到,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("这是其余的代码逻辑!"); }}Java中的异常

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("这是其余的代码逻辑!"); }}如果我们要捕获的多个异常之间存在父子类关系,那一定是子类在前,父类在后。因为父类异常类包含所有的子类异常类,所以你把父类写在前面,会把所有有关的异常都会进行捕获,那么后面写的子类的捕获也就没有任何的意义了。Java中的异常

如图,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("这是其余的代码逻辑!");//这一句会执行 }}Java中的异常

其实这个过程就相当于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("这是其余的代码逻辑!"); }}Java中的异常

可以看到,当我们发生异常之后,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("这是其余的代码逻辑!"); }}Java中的异常

可以看到,当我们将异常处理好后,直接使用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(); } }}Java中的异常

因为是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(); } }}Java中的异常

正如上面的代码所展示,我们可以自定义异常类,如果是继承于Exception类,那么你自定义的这个异常就是一个受查异常,如果是继承于RuntimeExpection,那么你自定义的就是一个非受查异常。这个自定义异常类的内部,根据你自己的需求去定义它的构造函数。

Java中的异常

  • 发表于 2022-10-30 00:56:10
  • 阅读 ( 158 )
  • 分类:热点

0 条评论

请先 登录 后评论

你可能感兴趣的文章

相关问题