Java-Day-15( 异常 )

发布时间 2023-04-24 11:28:49作者: 朱呀朱~

Java-Day-15

异常

引出异常

( Exception )

  • 如:零成分母时,会抛出 ArithmeticException 异常,然后程序就会崩溃退出,下面的代码也就不执行了

    • 但这种不出现致命错误就使得系统崩溃就不合理了
    • 所以设计者提供了异常处理机制来解决此问题
  • 解决方式 — 异常捕获

    • 如果程序员认为一段代码可能出现异常 / 问题,可以使用 try-catch 异常处理机制来解决,从而保证程序的健壮性

    • 选中代码后快捷键 ctrl + alt + t 选中 try - catch

    • 处理后即使出现了异常,程序仍可以继续执行

      int n1 = 1;
      int n2 = 0;
      try {
          int res = n1 / n2;
      } catch (Exception e) {
          System.out.println("出现异常的原因是:" + e.getMessage());
      }
      
  • 异常介绍

    • java语言中,将程序执行中发生的不正常情况称为 ”异常“。
      • 开发过程中语法的错误和逻辑错误不是异常
  • 执行过程中所发生的异常事件可分为两类

    • Error ( 错误 ):Java 虚拟机无法解决的严重问题,如:JVM 系统内部错误、资源耗尽等严重情况。
      • StackOverflowError [ 栈溢出 ] 和 OOM ( out of memory ),Error 是严重错误,程序会崩溃
    • Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,如:空指针访问,试图读取不存在的文件,网络连接中断等待
      • 往下还分两大类:运行时异常 [ 程序运行时,发生的异常 ] 和编译时异常 [ 编程时,编译器检查出的异常 ]
  • 异常体系图

    • idea 里,在 Throwable 源码里右键打开类图

      image-20230423185850874

    • 查找分支

      image-20230423185924488

    • 完成异常体系图

      image-20230424105601284

    • 虚线:实现类箭头指向的接口

    • 实线:继承了箭头指向的类

  • Java 源程序 —— ( 编译异常 ) ——> 字节码文件 —— ( 运行异常 ) ——> 内存中加载、运行类

  • 异常分为两大类,运行时异常和编译时异常

    • 运行时异常,是编译器不要求强制处置的异常,一般是指编程时的逻辑错误,是程序员应该避免其出现的异常
      • 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
    • 而编译时异常是编译器要求必须处置的异常
      • 一般像 idea 都会红色波浪线标出来

常见运行异常

  • NullPointerException 空指针异常:当应用程序试图在需要对象的地方却使用了 null 时,抛出该异常

    String name = null;
    System.out.println(name.length());
    
  • ArithmeticException 数学运算异常:当出现异常的运算条件时,抛出此异常

    int n1 = 1;
    int n2 = 0;
    int res = n1 / n2;
    
  • ArrayIndexOutOfBoundsException 数组下标越界异常:用非法索引访问数组时抛出的异常

    • 缺少命令行参数
    int[] arr = {1, 2};
    // for (int i = 0; i < arr.length; i++) { 正确
    for (int i = 0; i <= arr.length; i++) {
        //  数组从0开始,只有0、1,到不了2
    }
    
  • ClassCastException 类型转换异常:当试图将对象强制转换为不是实例的子类时,抛出该异常

    class B extends A {}
    class C extends A {}
    
    // main
    A b = new B();
    C c = (C)b;
    
  • NumberFormatException 数字格式不正确异常:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常 ( 使用异常来确保输入是满足条件的数字 )

    String name = "啦啦啦";
    int num = Integer.parseInt(name);
    

编译异常

  • 编译期间就必须处理的异常,否则代码不能通过编译
  • 常见的编译异常
    • SQLException // 操作数据库时,查询表可能发生异常
    • IOException // 操作文件时发生的异常
    • FileNoFoundException // 操作一个不存在的文件时发生的异常
    • ClassNotFoundException // 加载不存在的类时
    • EOFException // 操作文件到文件末尾,发生异常
    • IllegalArguementException // 参数异常

异常处理

  • 以下两种处理方式二选一

  • try-catch-finally

    • 程序员在代码中捕获发生的异常,自行处理

      try {
      //    代码 / 可能有异常
      } catch(Exception e) {
      //    捕获到异常
      //    当异常发生时,系统将异常封装成 Exception 对象 e,传递给 catch
      //    得到异常对象后,程序员自行处理
      //    如果没有发生异常,此处代码块不执行
      } finally {
      //    不管 try 代码块是否有异常发生,始终要执行 finally,也可以选择不写 finally
      //    所以通常将释放资源的代码放在此处
      }
      
    • 注意细节

      • 如果异常发生,就不再继续执行后面代码,直接进入 catch 块
      • 如果异常没有发生,则顺序执行 try 代码块,不进 catch
      • 如果希望不管是否发生异常都执行某段代码,用 finally
    • 如果 try 代码有多个异常,就使用多个 catch 分别捕获不同的异常,相应处理

      • 但要求子类异常写在前面,父类异常写在后面

        catch { ... } catch

    • 可以直接 try-finally,但这样相当于没有捕获异常,因此程序会直接崩掉 / 退出

    try{
        int n1 = 10;
        int n2 = 0;
        System.out.println(n1 / n2)
    }finally {
        System.out.println("执行了finally");
    }
    System.out.println("程序继续执行");
    
    // 若是n2不为零,就会输出计算结果和两条System输出语句
    // 如上述代码的话,就只输出:“执行了finally” 
    // 后面一切代码包括后面的System都不会执行
    
    • 应用:不管是否发生异常,都必须执行某个业务逻辑
  • throws ( 默认方式 )

    • 如果一个方法 ( 中的语句执行时 ) 可能生成某种异常,但并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而交给该方法的调用者来处理,最顶级的处理者就是 JVM

      • JVM 处理:输出异常信息,退出程序.
      • 就是平常默认的出错方式
    • 在方法的声明中用 throws 语句可以声明抛出异常的列表,throws 后面的异常类型可以是方法中产生的异常类型,也可以是异常的父类 ( Exception )

      public class test {
          public static void main(String[] args){
          }
      //      使用第二种方法:throws,抛出异常,让调用f1方法的调用者(方法)处理
      //    public void f1() throws FileNotFoundException {
      //      throws后面也可以是异常列表,即可以抛出多个异常
      //    public void f1() throws FileNotFoundException,NullPointerException {
      //      或者直接用异常的父类,全包括了
          public void f1() throws Exception {
              FileInputStream fis = new FileInputStream("d://aa.txt");
          }
      }
      
    • 注意细节

      • 编译异常必须要处理 ( 会标红 )

      • 对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理 ( 最终就是抛给了 JVM )

      • 子类重写父类方法时,对抛出异常的规定:子类重写的方法,所抛出的异常要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型

        // 从先前画的异常体系图可以看出NullPointerException是RuntimeException的子类
        class Father {
            public void method() throws RuntimeException { }
        }
        
        class Son extends Father {
            @Override
            public void method() throws NullPointerException { }
        }
        
      • 编译异常时,方法显示地声明抛出异常,表明该方法将不对这些异常进行处理,而交给该方法的调用者来处理,则调用者 ( 即下述的 f2() ) 要不 try-catch,要不继续 throws

         public void f1() throws FileNotFoundException {
             FileInputStream fis = new FileInputStream("d://aa.txt");
            }
        
        // 若是这个时候有一个方法调用了这个抛出异常的方法f1
        // 只这样写会报错
        // public static f2() {
        // F1:
        public static f2() throws FileNotFoundException {
            f1();
        }
        
        // F2:
        public static f2() {
            try {
                f1();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        
      • 运行异常时,是存在默认处理的,而 java 中并不要求程序员显式处理,因为有默认处理机制

        public static f3() throws AirthmeticException {
        }
        public static f4() {
        // 不会报错
            f3();
        }
        
  • 练习

    • 看输出什么

      public class test {
          public static void main(String[] args){
              System.out.println(Exception.method());
          }
      }
      
      class Exception {
          public static int method(){
              int i = 1;
              try {
                  i++;   // i=2
                  String[] names = new String[3];
                  if (names[1].equals("tom")) { // 空指针错误直接跳到Null...
                      System.out.println(names[1]);
                  } else {
                      names[3] = "zhuyazhu";
                  }
                  return 1;
              } catch (ArrayIndexOutOfBoundsException e) {
                  return 2;
              } catch (NullPointerException e){
                  return ++i;  // i=3,但还有finally,return不会马上执行,在底层会临时变量temp=3保存
              } finally {
                  ++i;   // i=4
                  System.out.println("i=" + i);  // 输出 i=4
              }
          }
      }
      
      • 输出为
        i=4
        3

      • 如果没有出现异常,则执行 try 块中的所有语句,不执行 catch 块中语句,如果有 finally,最后还需要执行 finally 里的语句

      • 如果出现异常,则 try 块中异常发生后,try 块剩下的语句不再执行,将执行 catch 块中的语句,如果有 finally,最后还需要执行 finally 里的语句

      • 如果没有 catch 捕获,就 finally 后强行退出程序 ( 如果有 finally 的话 ),捕获了的话就继续执行

    • 如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止

      public class test {
          public static void main(String[] args){
              Scanner scanner = new Scanner(System.in);
              int num = 0;
              while(true){
                  try {
                      System.out.println("请输入一个整数:");
                      num = Integer.parseInt(scanner.next());
                      break;
                  } catch (NumberFormatException e) {
                      System.out.println("你输入的不是个整数");
                  }
              }
              System.out.println("你输入的值是=" + num);
          }
      }
      

自定义异常

  • 当程序出现了某些 “ 错误 ”,但该错误信息并没有在 Throwable 子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息

    • 好处就是可以使用默认的处理机制
  • 自定义异常的步骤

    • 定义类:自定义异常类名 ( 程序员自己写 ) 继承Exception 或 RuntimeException
    • 如果继承 Exception,属于编译异常
      • 若是用编译异常,就要在调用其的方法里 ( 如:main ) 也要 throws 自定义的异常
    • 如果继承 RuntimeException,属于运行异常 ( 一般来说,继承 RuntimeException )
  • 应用

    • 当接收 Person 对象年龄时,要求范围在 18 ~ 120 之间,否则抛出一个自定义异常 ( 要求继承 RuntimeException ),并给出提示信息

      public class test {
          public static void main(String[] args) {
              int age = 80;
              if (!(age >= 18 && age <= 120)) {
                  throw new AgeException("年龄错误,应该在18~120之间");
              }
              System.out.println("输入年龄范围正确");
          }
      }
      
      //自定义异常
      class AgeException extends RuntimeException {
          public AgeException(String message) {
      //        追寻父类是一直到Throwable类,其方法即为将message信息显示于控制台提示
              super(message);
          }
      }
      
  • throw 和 throws 区别

    意义 位置 后面所跟
    throws 异常处理的一种方式 方法声明处 异常类型
    throw 手动生成异常对象的关键字 方法体中 异常对象
    • 如:在上述自定义异常时有一句 throw new AgeException("年龄错误,应该在18~120之间");
  • 练习

    • 查看输出

      public class test {
          public static void main(String[] args) {
              try {
                  ReturnExceptionDemo.methodA();
      //            从methodA中拿到throw出的异常被下面的catch捕获
              } catch (Exception e) {
      //            message:”制造异常“,在这里才被输出
                  System.out.println(e.getMessage());  								// 3
              }
              ReturnExceptionDemo.methodB();
          }
      }
      
      //自定义异常
      class ReturnExceptionDemo {
          static void methodA() {
              try {
                  System.out.println("进入方法A");  							   	 	 // 1
                  throw new RuntimeException("制造异常");  							 // 3
              } finally {
                  System.out.println("用A方法的finally");  							 // 2
              }
          }
          static void methodB() {
              try {
                  System.out.println("进入方法B"); 									  // 4
                  return;
              } finally {
                  System.out.println("用B方法的finally"); 							  // 5
              }
          }
      
      
      • 输出

        进入方法A
        用A方法的finally
        制造异常
        进入方法B
        用B方法的finally

    • 编写应用程序,接收命令行的两个参数,计算两数相除,要求使用 cal (int n1, int n2),对数据格式不正确、缺少命令行参数、除零进行异常处理

      public class test1 {
          public static void main(String[] args) {
              try {
                  if (args.length != 2) {
                      throw new ArrayIndexOutOfBoundsException("参数个数不对");
                  }
                  int n1 = Integer.parseInt(args[0]);
                  int n2 = Integer.parseInt(args[1]);
                  double res = cal(n1, n2);
                  System.out.println("计算结果为=" + res);
      
              } catch (ArrayIndexOutOfBoundsException e) {
                  System.out.println(e.getMessage());
              } catch (NumberFormatException e) {
                  System.out.println("参数格式不正确,需要输入整数");
              } catch (ArithmeticException e) {
                  System.out.println("出现了除以零的异常");
              }
          }
          public static double cal(int n1, int n2) {
              return n1 / n2;
          }
      }