• JavaSE学习值之--认识异常


     💕"有效知识的前提是承认知识边界,承认我们对边界那边的一切无可奉告。"💕

    作者:Mylvzi 

     文章主要内容:JavaSE学习值之--认识异常 

    一.什么是异常?

      异常就是程序在运行的时候产生的不正常的行为  小细节没注意到,发生了小的错误,比如:

    1.算数异常

            System.out.println(10/0);
    

    2.空指针异常  null.length

    1. int[] arr = null;
    2. System.out.println(arr.length);

    3.数组越界异常

    1. int[] arr = new int[3];
    2. System.out.println(arr[5]);


      

      可见Java中java中不同类型的异常,都有与其对应的类来进行描述,实际上Java中的异常是一个大的体系

    二.异常的体系:

    异常它是由编译器识别并给出的,证明其存在于编译器所自带的库文件中,应该是一个类

    1. Throwable:

      是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception

    2. Error:

      指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表: StackOverflowError和OutOfMemoryError,一旦发生回力乏术。(递归调用时)

    3. Exception:

      异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说 的异常就是Exception。

    三.异常的分类:

      主要是分为两大类 

    • 运行时异常(不受查异常)
      RuntimeException()
    • 编译时异常(受查异常)

    1. RunTimeException 包括他的所有子类

    在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常(Unchecked Exception)

    注意:

      在编写代码过程中出现的语法错误不是运行时异常,比如把main写成了mian,编译器在编译的时候就会报错,这叫错"编译期错误",而运行时异常的产生是所写代码已经经过编译生成.class文件了,再交由JVM运行时产生的错误

    补充:RuntimeException的子类

    2.编译时异常

      在编译的时候就会发生的异常,又称作受查异常(Checked Exception)

    比如之前实现的Cloneable接口

    1. class Stu implements Cloneable {
    2. String name;
    3. int age;
    4. public Stu(String name, int age) {
    5. this.name = name;
    6. this.age = age;
    7. }
    8. @Override
    9. protected Object clone() throws CloneNotSupportedException {
    10. return super.clone();
    11. }
    12. @Override
    13. public String toString() {
    14. return "Stu{" +
    15. "name='" + name + '\'' +
    16. ", age=" + age +
    17. '}';
    18. }
    19. }
    20. public class Test1 {
    21. public static void main(String[] args){
    22. Stu stu = new Stu("lvzi",18);
    23. Stu stu2 =(Stu) stu.clone();
    24. System.out.println(stu2);
    25. }

    之前提到,必须在main方法的签名中声明抛出异常,否则会出现异常

     这种在编译阶段就报错的异常叫做编译时异常,又叫做受查异常(Checked Exception)

    四.异常的处理

    1.防御式编程

        错误的代码总是存在且不可避免地,我们要做的就是当程序出现异常的时候及时告诉程序员,来帮助他修改代码,主要有两种思路:

    1.LBYL: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型

      即对每一个流程都充分想到其可能会发生的错误,及时处理

    1. boolean ret = false;
    2. ret = 登陆游戏();
    3. if (!ret) {
    4. 处理登陆游戏错误;
    5. return;
    6. }
    7. ret = 开始匹配();
    8. if (!ret) {
    9. 处理匹配错误;
    10. return;
    11. }
    12. ret = 游戏确认();
    13. if (!ret) {
    14. 处理游戏确认错误;
    15. return;
    16. }
    17. ret = 选择英雄();
    18. if (!ret) {
    19. 处理选择英雄错误;
    20. return
    21. }

      每进行一步就进行一次检查,但这种方式使正确代码和异常检查的代码混在一起,使得整个代码很混乱,不推荐

    2.EAFP: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操 作, 遇到问题再处理. 即:事后认错型 

    1. try {
    2. 登陆游戏();
    3. 开始匹配();
    4. 游戏确认();
    5. 选择英雄();
    6. 载入游戏画面();
    7. ...
    8. } catch (登陆游戏异常) {
    9. 处理登陆游戏异常;
    10. } catch (开始匹配异常) {
    11. 处理开始匹配异常;
    12. } catch (游戏确认异常) {
    13. 处理游戏确认异常;
    14. } catch (选择英雄异常) {
    15. 处理选择英雄异常;
    16. } catch (载入游戏画面异常) {
    17. 处理载入游戏画面异常;
    18. }

      EAFP的思维简单来说就是管他三七二十一,先执行再说,产生的异常事后处理即可

    这种思维是的操作部分和异常处理部分分离,让程序员更加模块化的思考,更有利于代码的编写!!!

    在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。 

      在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者,比如:参数检测

      在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下: 

    throw new XXXException("异常产生的原因")

     举例:

    1. public static int[] arr ={1,2,3};
    2. public static int getElem(int index) {
    3. if(index < 0 || index > arr.length) {
    4. throw new ArrayIndexOutOfBoundsException(index + "索引不正确,数组越界");
    5. }
    6. return arr[index];
    7. }
    8. public static void main(String[] args) {
    9. int[] arr ={1,2,3};
    10. System.out.println(getElem(0));// 正确 输出1
    11. System.out.println(getElem(10));// 异常
    12. System.out.println(getElem(1));// 不输出
    13. }

     注意:

    • throw必须写在方法内部
    • 抛出的异常必须是Exception或Exception的子类
    • 如果抛出的异常是RuntimeException或其子类,不需要处理,直接交给JVM处理
    • 如果抛出的异常是受查异常,必须通过throws进行处理,否则无法通过编译
    • .异常一旦抛出,其后的代码就不会执行

    五.异常的捕获 

       异常的捕获 就是异常处理的具体方式,包括两种方式

    • try,catch语句
    • throws声明

    1.异常声明throws 

      处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛 给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。

    语法格式: 修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{ } 

     比如上文的Cloneable接口的实现:

    注意:

    •  throws必须跟在方法的参数列表之后
    • 声明的异常必须是 Exception 或者 Exception 的子类
    • 方法内部抛出了多个异常,throws也要抛出多个异常,异常之间通过”,“隔开;如果异常之间存在父子关系,只需声明父类异常即可
    1. public class Config {
    2. File file;
    3. // public void OpenConfig(String filename) throws IOException,FileNotFoundException{
    4. // FileNotFoundException 继承自 IOException
    5. public void OpenConfig(String filename) throws IOException{
    6. if(filename.endsWith(".ini")){
    7. throw new IOException("文件不是.ini文件");
    8. }
    9. if(filename.equals("config.ini")){
    10. throw new FileNotFoundException("配置文件名字不对");
    11. }
    12. // 打开文件
    13. }
    14. public void readConfig(){
    15. }
    16. }
    • 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出 

    2 try-catch捕获并处理 

      throws声明异常并没有处理异常,只是将异常交给调用者去处理,一直传递给JVM虚拟机;要想真正处理异常,就需要try-catch语句

    注意:

    1.try中存放可能出现异常的代码,也就是说不一定会抛出异常

    2.catch()存放异常的类型,{}内部存放处理的代码,处理完成后会继续执行后续代码;如果没有捕获到异常,则catch里的语句并不会被执行

    3.finally中的语句一定会被执行, 

    4.打印异常有三种

    5.如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM收到后中断程序----异常是按照类型来捕获的 

       

    6.如果异常之间存在父子关系,子类异常被捕获时在前,父类在后,如果顺序颠倒,子类异常的捕获就没有任何意义了

    1. public static void main(String[] args) {
    2. int[] arr = {1, 2, 3};
    3. try {
    4. System.out.println("before");
    5. arr = null;
    6. System.out.println(arr[100]);
    7. System.out.println("after");
    8. } catch (Exception e) { // Exception可以捕获到所有异常
    9. e.printStackTrace();
    10. }catch (NullPointerException e){ // 永远都捕获执行到
    11. e.printStackTrace();
    12. }
    13. System.out.println("after try catch");
    14. }

     7.无论如何,finally中的代码一定会被执行

    1. public static int[] arr ={1,2,3};
    2. public static int getElem(int index) {
    3. return arr[index];
    4. }
    5. public static void main(String[] args) {
    6. int[] arr ={1,2,3};
    7. try{
    8. System.out.print(getElem(100));// 异常
    9. }catch (Exception e) {
    10. e.printStackTrace();
    11. }finally {
    12. System.out.println("finally中的代码一定会被执行");
    13. }
    14. }

      在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库 连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能 导致有些语句执行不到,finally就是用来解决这个问题的。 

    思考:那我们该如何记录并处理异常呢?什么时间去处理异常呢?

      在目前我们所写的代码中,常常采取记录错误日志的方式来记录异常,通过编译器的报错告诉程序员出错的地方

    六.如何设计一个自定义类型的异常?

      将你可能出现的异常设计为一个继承ExceptionRunTimeException的类,添加一个带参数的构造方法(用于打印错误信息),在调用时,一旦出错就抛出对应的异常即可

      注意:继承Exception的类是受查异常,必须在调用的方法声明中添加throws该异常

    继承RunTimeException的类是非受查异常,不需要添加声明

    e.printStackTrace();打印异常信息

    1. class UsernameException extends RuntimeException {
    2. public UsernameException(String message) {
    3. super(message);
    4. }
    5. }
    6. class PasswdException extends RuntimeException {
    7. public PasswdException(String message) {
    8. super(message);
    9. }
    10. }
    11. class Login {
    12. private String Username;
    13. private String Passwd;
    14. public static void logininfo(String Username,String Passwd) {
    15. if(!Username.equals("admin")) {
    16. throw new UsernameException("用户名错误");
    17. }else if(!Passwd.equals("123456")) {
    18. throw new PasswdException("密码错误");
    19. }
    20. }
    21. }
    22. public class Test1 {
    23. public static void main(String[] args) {
    24. try {
    25. Login.logininfo("admin2","123456");
    26. }catch (UsernameException ex) {
    27. ex.printStackTrace();
    28. }catch (PasswdException ex) {
    29. ex.printStackTrace();
    30. }
    31. }
    32. }

    异常总结:

    1.throw抛出一个异常

      用于参数检测  一旦出错就可以达到终止程序运行的操作

    比如传递数组和数组的下标

    1.如果数组是null  直接抛出空指针异常,程序停止运行

    2.如果下标超过数组的范围,直接抛出数组越界异常,程序停止运行

    之前:出现这种错误,只打印错误的信息,比如arr == null  sout("数组为空"),这种方式太温柔了,只告诉你有错误,但不会终止运行;直接抛异常,既能知道错误的信息,又能终止程序的运行

    2.异常处理关键字的理解

    throws:是一种甩锅行为,通过在方法的生命中添加throws+异常种类,将这个可能出现的异常交给调用者处理,而调用者可以在他的方法声明时添加throws异常,交给jvm去处理,就是一种甩锅,但很方便,不需要设置try catch

    try catch finally:   throws并没有处理异常,只是甩锅给其他人;try catch是一种自己解决异常的方法,通过catch捕获异常并记录异常的日志  所谓的捕获异常就是解决该异常(异常都是被抛出的,catch接受抛出的异常,给他限制在笼子里,这样就没有异常了)

    finally:有些代码无论程序是否异常都必须要被执行(比如电脑就算连不上网,也要能看到界面啊?再比如,校园网欠费,无法上网,但是我还能打开缴费网站),如果打不开那我怎么解决上网的问题?所以finally就是用来解决这种任何情况下都要执行的代码(也就是说无论你欠不欠费都能打开缴费网站)

     对于已受查异常来说,其异常会在编译的时候发现,必须通过tycatch语句捕获或者在方法签名使用throws抛出

    3.异常处理的流程

    • 程序先执行 try 中的代码 如果 try 中的代码出现异常, 就会结束 try 中的代码,
    • 看和 catch 中的异常类型是否匹配. 如果找到匹配的异常类型, 就会执行 catch 中的代码
    • 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
    • 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
    • 如果上层调用者也没有处理的了异常, 就继续向上传递.
    • 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止

  • 相关阅读:
    Clion+Qt,在运行exe文件时出现黑窗口的解决方案
    pdf如何盖电子骑缝章?
    AI全栈大模型工程师(十六)智能体架构:Agent
    计算机毕业设计springboot+vue+elementUI校园志愿者管理系统
    数据的力量:Facebook如何通过数据分析驱动创新
    电脑重装系统word从第二页开始有页眉页脚如何设置
    datagrip 相关数据连接信息无缝迁移
    【JavaScript复习十五】数组小练习
    蓝桥杯 第 1 场算法双周赛 第1题 三带一 c++ map 巧解 加注释
    你真会判断DataGuard的延迟吗?
  • 原文地址:https://blog.csdn.net/Mylvzi/article/details/133693216