day07-异常

今日目标 :

  • 异常的概述
  • 异常的分类
  • 异常的处理方式
  • 自定义异常

    1 异常的概述

    1.1 什么是异常?

  • 异常就是程序出现了不正常情况 , 程序在执行过程中 , 数据导致程序不正常 , 最终导致了JVM的非正常停止

  • 注意 : 语句错误不算在异常体系中

    1.2 异常的存在的形式

  • 异常有类型之分,比如我们之前有接触过的比较熟悉的数组越界异常(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException),类型转换异常(ClassCastException)。当程序中产生异常时,其实就是在异常的位置创建了一个该异常的对象,该对象携带了相关的异常信息。

    • 简单来说 : 异常就是Java中提供的类的对象

      1.3 程序中异常产生后,是如何处理的

  • 程序中一旦产生异常,首先会中断向下执行。异常的传递要根据处理方式(后面章节会讲到)而定,如果没有处理,默认是将异常传递给本方法的调用者。不断往回传递,直到JVM收到该异常,此时程序终止执行

    2 异常的分类

    异常 :就是程序出现数据不合法或者其他情况,导致了程序不正常执行的情况,最终中断执行。
    异常体系:
    day07 异常 - 图1

    1. Error:严重问题,通过代码无法处理。比如:内存溢出。
    2. Exception:称为异常类,它表示程序本身可以处理的问题
  • 编译时期异常(Exception及非RuntimeException子类)

    • 在编译成class文件时必须要处理的异常,不处理会报错编译失败,也称之为受检异常。
  • 运行时期异常(RuntimeException及其子类)

    • 在编译成class文件时,不用强制处理的异常,也称之为非受检异常

      3 异常的处理方式

      3.1 JVM处理异常的方式

  • 如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理 , 那么JVM是如何处理的呢 ?

    • 把异常的类型 , 原因 , 位置打印在控制台
    • 程序停止执行
  • 注意 : 程序中出现了异常 , 会在当前位置创建此异常的对象 , 对象中包含了异常的信息 , 并把此异常交给本方法的调用者处理
  • 缺点 : 用户体验不好

    package com.itheima.exception_demo;
    /*
      JVM 的默认处理方案 :
      如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理 , 那么JVM是如何处理的呢 ?
          1 在控制台以红色字体打印异常的  类型  +  原因  +  位置
          2 终止了程序
    */
    public class ExceptionTest_jvm {
      public static void main(String[] args) {
          int[] arr = {33, 22, 11};
          // 此位置会发生数组索引越界异常 , 那么会在此位置创建一个
          // ArrayIndexOutOfBoundsException异常对象 , 异常对象携带了异常信息
          // 我们自己没有手动处理异常 , 那么此异常会交给方法的调用者处理
          System.out.println("执行了吗?  1");
          show(arr);
          System.out.println("执行了吗? 4");
      }
      public static void show(int[] arr) {
          System.out.println("执行了吗? 2");
          System.out.println(arr[100]);// 自己 还是 JVM 创建的异常对象? JVM
          System.out.println("执行了吗? 3");
      }
    }
    

    3.2 手动处理异常方式

    3.2.1 声明异常 throws

  • 声明异常—— throws ```java 修饰符 返回值类型 方法名(参数列表) throws 异常类型1 ,类型2,…{ …
    } //举例 : public void show() throws NullPointerException , ArrayIndexOutOfBoundsException {

}


- 作用 :
   - 表示调用当前的方法可能会出现某些异常,使用时需要注意哦!
   - 如果当前方法没有出现任何异常, 那么代码会正常执行
   - 如果当前方法中出现了异常 , 会把异常交给本方法调用者处理(甩锅)
- 代码演示<br />**练习 : 定义两个方法一个运行时期异常 , 一个声明编译时期异常** **!**
```java
package com.itheima.exception_demo.throws_demo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
    1 声明异常—— throws
        格式 : 修饰符   返回值类型  方法名(参数列表) throws 异常类型1 , 异常的类型2...   {  ...  }
        举例 : public void show() throws NullPointerException , ArrayIndexOutOfBoundsException  { .... }
        作用 :
            1 表示告知调用者当前的方法可能会出现某些异常,使用时需要注意哦!
            2 如果当前方法没有出现任何异常, 那么代码会正常执行
            3 如果当前方法中出现了异常 , 会把异常交给本方法调用者处理(甩锅)
    2 需求 :
        练习 : 定义两个方法一个运行时期异常 , 一个声明编译时期异常 !
    3 注意 :
        1 编译时异常因为在编译时就会检查,所以必须要写在方法后面进行显示声明
        2 运行异常不用强制处理,所以在方法后面可以不写
        3 如果声明多个异常有子父类关系 , 那么只要声明一个父类即可(多态)
 */
public class Exception_Throws {
    public static void main(String[] args) throws Exception  {
        int[] arr = {11, 22, 33};
        test1(arr, 0);
        test2("1998-10/10");
    }
    public static void test1(int[] arr, int index) {
        //运行时异常,ArrayIndexOutOfBoundsException
        System.out.println(arr[index]);//如果index索引越界,也会报错
    }
    public static void test2(String dateStr) throws Exception/*, ParseException*/ {//将异常甩锅给调用者
        //日期格式化对象
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        //利用日期格式化对象,解析字符串为日期对象
        //编译时异常,ParseException
        Date date = sdf.parse(dateStr);
        System.out.println("date = " + date);
    }
}

3.2.2 抛出异常 throw

  • 思考:
    • 以前出现了异常,虚拟机帮我们创建一个异常对象,抛给调用者。但是如果我们需要自己手动创建一个异常对象该如何写?
  • 格式 :

    修饰符    返回值类型    方法名(参数列表)  {
      throw new 异常对象();
    }
    
  • 注意 :

    • 抛出异常的格式必须在方法的内部完成
    • 如果手动抛出一个异常,下面的代码无法执行
  • 作用 :
    • 在方法中,当传递的参数有误,没有继续运行下去的意义了,则采取抛出处理,表示让该方法结束运行。
    • 告诉调用者方法中出现的问题原因
  • 案例演示
    练习 : 定义一个方法 , 方法的参数接收一个数组 , 在方法中遍历数组 .
    需求1 : 如果方法接收的数组为null , 使用输出语句提示
    需求2 : 如果方法接收的数组为null , 使用抛出异常解决
    思考 : 两种方式的区别在哪里 ?

    3.2.3 throws和throw的区别

  • throws :

    • 使用时在方法声明后面,跟上异常类名
    • 含义:告知调用者调用该方法有可能会出现这样的异常【甩锅】,将本方法内部的异常传递给调用者。
  • throw :

    • 使用时在方法体内,跟的是异常对象名
    • 含义:逻辑代码实现过程中,检查数据不合法是可以产生异常,告知调用者数据传入有误

      3.2.4 捕获异常

  • 捕获处理异常介绍 : try, catch

    • 之前的声明或者抛出都是将异常传递出去,让调用者知道异常信息。
      而捕获处理是本方法内部进行处理 , 能够阻止异常的传递,从而保证程序能够继续往下执行。
  • 捕获异常的格式

    try {
      try中存放可能会出现问题的代码
          1.代码...
          2.代码...
          3.代码...
    } catch (异常类型 变量名) {
          4.处理异常方案
         打印异常,获取异常原因记录日志......)
    }
    5.其他代码...
    
  • 执行方式

    • 如果 try 中没有遇到问题,怎么执行?
      从上往下依次执行 , catch中不执行。 1,2,3,5
    • 如果 try 中代码2遇到了问题,问题下面的代码还会执行吗?
      不会执行,会拿当前异常对象和异常类型匹配,匹配成功执行处理异常代码 1,2,4,5
    • 如果出现的问题没有被捕获,那么程序如何运行?
      如果异常没有捕获到 , 虚拟机会帮助我们处理 1,2
    • 同时有可能出现多个异常怎么处理?
  • 多异常捕获处理方案

    • 多个异常,每个异常单独处理

      try{
      异常1
      }catch(异常1){
      } 
      try{
      异常2
      }catch(异常2){
      }
      
    • 多个异常,一次捕获,多次处理

      try{
      异常1
      异常2
      }catch(异常1){
      }catch(异常2){
      }
      
    • 多个异常,异常一次捕获,一次处理

      try{
      异常1
      异常2
      }catch(Exception e){
      }
      

      3.3 捕获和声明的练习

      定义一个方法接收一个生日日期字符串(xxxx年xx月xx日),main方法中让用户输入一个生日日期字符串,调用设计好的方法计算在地球上活了多少天。

要求:如果解析发生异常,捕获异常,提示用户要重新输入生日日期字符串,直到输入正确的日期为止。
思考:设计代此码的过程中想想什么时候捕获异常,什么时候声明异常?
结论:一般提供数据逻辑捕获异常(上层逻辑),处理数据的逻辑抛出异常(底层逻辑)。

package com.itheima.exception_demo.exception_test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class ExceptionTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (true) {

            System.out.println("请输入您的生日字符串:");
            String bdStr = sc.nextLine();
            //调用方法获取天数
            long days = 0;//该捕获,还是声明甩锅? 背锅。数据我们自己提供,自己背锅。
            try {
                days = getDays(bdStr);
                System.out.println("您在地球上活了:" + days + "天");
                break;//数据正常,结束循环
            } catch (ParseException e) {
                System.out.println("不好意思,您输入的生日格式有误,请重新输入:");
            }

        }

        System.out.println("程序运行结束,欢迎下次光临!!");

    }

    // 接收的生日日期 , 计算活了多少天
    public static long getDays(String birthdayStr) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = sdf.parse(birthdayStr);//该捕获,还是声明甩锅? 甩锅,数据不是自己提供,不背锅。数据谁提供,谁背锅。
        long t1 = date.getTime();
        long t2 = System.currentTimeMillis();

        return (t2-t1)/1000/60/60/24;
    }

}

3.4 Throwable 的成员方法

方法名 说明
public String getMessage() 返回此 throwable 的详细消息字符串
public String toString() 返回此可抛出的简短描述
public void printStackTrace() 把异常的错误信息输出在控制台
package com.itheima.exception_demo.exception_test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/*
    Throwable类:
        public String getMessage()    : 会把异常的  原因 打印在控制台
        public String toString()      : 会把异常的  类型 + 原因打印在控制台
        public void printStackTrace() :    把异常的错误信息输出在控制台
 */
public class ExceptionMethod {
    private static final Logger LOGGER = LoggerFactory.getLogger("ExceptionMethod");


    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println(arr[5]);
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println("越界异常被捕获");
            // e 就是捕获的异常对象
            //e.printStackTrace();// 打印异常栈信息

            //String message = e.getMessage();//获取异常的信息
            //System.out.println("message = " + message);

            String s = e.toString();//简短异常信息:异常类型和异常的信息
            System.out.println("s = " + s);
            //java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3

            //通过Logback把异常记录到日志中,方便以后查看
            LOGGER.error("数据越界了!!",e);


        }

    }
}

4 自定义异常

4.1 概述

  • 当JDK中的异常类型,不满足实际的业务需要时。就可以自己定义异常。例如,学生的年龄数据,如果是负数或者数据 超过了150认为是不合法的,就需要抛出异常。JDK中就没有表示年龄的异常,就需要自己定义异常了

    4.2 实现步骤

  • 定义异常类

  • 写继承关系
  • 空参构造
  • 带参构造

    4.3 自定义异常注意

  • 如果要自定义编译时异常,就继承Exception

  • 如果要自定义运行时异常,就继承RuntimeException

比如定义年龄异常类

package com.itheima.exception_demo.myexception_demo;
//写一个类继承异常类
public class AgeOutOfBoundsException extends RuntimeException{
    //构造方法写好

    public AgeOutOfBoundsException() {
    }

    /**
     * @param message 描述异常的详细信息
     */
    public AgeOutOfBoundsException(String message) {
        super(message);
    }

    public AgeOutOfBoundsException(String message, Throwable cause) {
        super(message, cause);
    }

    public AgeOutOfBoundsException(Throwable cause) {
        super(cause);
    }

    public AgeOutOfBoundsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
package com.itheima.exception_demo.myexception_demo;

public class Student {
    private String name;
    private int age;

    public Student(String name, int age)  {
        this.name = name;
        //年龄,必须是0~150之间
        if (age<0||age>150) {
            //给异常
            //throw new Exception("年龄不合法,必须在[0,150]");
            //throw new RuntimeException("年龄不合法,必须在[0,150]");
            //使用自己定义年龄异常
            throw new AgeOutOfBoundsException("年龄不合法,必须在[0,150]");
        }
        this.age = age;
    }

    public void setAge(int age) {
        //年龄,必须是0~150之间
        if (age<0||age>150) {
            throw new AgeOutOfBoundsException("年龄不合法,必须在[0,150]");
        }
        this.age = age;

    }
}
package com.itheima.exception_demo.myexception_demo;
/*
自己定义的异常和JDK的异常,使用是一样
 */
public class Demo {
    public static void main(String[] args) {
        //Student stu = new Student("张三",100);
        Student stu = new Student("张三",200);//异常数据
        System.out.println("stu = " + stu);
    }
}