day18_异常

异常概念

  • 异常,就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响。在程序中的意思就是指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。在Java语言中,将程序执行中发生的不正常情况称为“异常”。 (开发过程中的语法错误和逻辑错误不是异常)。

Java程序在执行过程中所发生的异常事件可分为两类:

  • ErrorJava虚拟机无法解决的严重问题。如:JVM系统内部错误、资源 耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
  • Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使 用针对性的代码进行处理。例如: 空指针访问 丶试图读取不存在的文件 丶网络连接中断 丶数组角标越界....对于这些错误,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。

异常体系

  • 异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Error 与 java.lang.Exception ,平常所说的异常指 java.lang.Exception 。

                              

Throwable中的常用方法:

  • public void printStackTrace() :打印异常的详细信息。包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
  • public String getMessage() :获取发生异常的原因。提示给用户的时候,就提示错误原因。
  • public String toString() :获取异常的类型和异常描述信息(不用)。

                      

异常分类

  • 运行时异常 :编译器不要求强制处置的异常。一般是指编程时的逻辑错误,应该积极避免其出现的异常java.lang.RuntimeException类及它的子类都是运行时异常。 对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
  • 编译时异常 :编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一 般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。对于这类异常,如果程序不处理,可能会带来意想不到的结果。

                                 

常见异常

常见的运行时异常

java.lang.RuntimeException

  • ClassCastException
  • ArrayIndexOutOfBoundsException
  • NullPointerException
  • ArithmeticException
  • NumberFormatException
  • InputMismatchException
  • 。。。

常见的编译时异常

  • java.io.IOExeption
  • java.lang.ClassNotFoundException
  • java.lang.InterruptedException
  • java.io.FileNotFoundException
  • java.sql.SQLException

​​​​​异常的产生过程解析 

先运行下面的程序,程序会产生一个数组索引越界异常ArrayIndexOfBoundsException。我们通过图解来解析下异常产生的过程。

package demo01;
 
public class ArrayTools {
    // 对给定的数组通过给定的角标获取元素。
    public static int getElement(int[] arr, int index) {
        int element = arr[index];
        return element;
    }
}

测试类

package demo01;
 
public class ExceptionDemo {
    public static void main(String[] args) {
        int[] arr = {34, 12, 67};
        int num = ArrayTools.getElement(arr, 4);
        System.out.println("num=" + num);
        System.out.println("over");
    }
}

上述程序执行过程图解:  

                             

Java异常处理的方式:

Java提供的是异常处理的抓抛模型

  • Java程序的执行过程中如出现异常,会生成一个异常类对象, 该异常对象将被提交给Java运行时系统,这个过程称为抛出 (throw)异常。

异常对象的生成

  • 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例 对象并抛出——自动抛出
  • 由开发人员手动创建:Exception exception = new ClassCastException();——创 建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样

异常的抛出机制

                                             

                                            为保证程序正常执行,代码必须对可能出现的异常进行处理。

  • 如果一个方法内抛出异常,则其后的代码不在执行。该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。 这一过程称为捕获(catch)异常。
  • 如果一个异常回到main()方法,并且main()也不处理,则程序运 行终止。
  • 程序员通常只能处理Exception,而对Error无能为力。

异常处理机制一:try-catch-finally

  • 异常处理是通过try-catch-finally语句实现的

格式:

                              

详解格式: 

try

  • 捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。

catch

  • (Exceptiontype e) 在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。

                   

我们可以使用Throwable中的常用方法输出异常

  • getMessage() 获取异常信息,返回字符串
  • printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。

                             

finally 

  • 捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。 不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。 finally语句和catch语句是任选的。

代码执行流程图

                   

举例

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
 
public class Demo {
    public static void main(String[] args) {
        //定义变量,提高访问权限
        FileInputStream fis = null;
        try {
            //可能出现异常的代码
            File file = new File("hello.txt");
            fis = new FileInputStream(file);
            int data = fis.read();
            while (data != -1) {
                System.out.print((char) data);
                data = fis.read();
            }
        } catch (Exception e) {
            //异常出现后的处理逻辑
            e.printStackTrace();
        } finally {
            try {
                //防止空指针异常
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
 
 
    }
}

总结一下

  1. finally是可选的。
  2. 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
  3. 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码
  4. catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
  5. catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
  6. 常用的异常对象处理的方式: ① String  getMessage()    ② printStackTrace()
  7. 在try结构中声明的变量,再出了try结构以后,就不能再被调用
  8. try-catch-finally结构可以嵌套
  9. 使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
  10. 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。
  11. 注意:finally不能单独使用。

不捕获异常时的情况

  • 前面使用的异常都是RuntimeException类或是它的子类,这些类的异常的特 点是:即使没有使用try和catch捕获,Java自己也能捕获,并且编译通过 ( 但运行时会发生异常使得程序运行终止 )。
  • 如果抛出的异常是IOException等类型的非运行时异常,则必须捕获,否则 编译错误。也就是说,我们必须处理编译时异常,将异常进行捕捉,转化为 运行时异常。

异常处理机制二:声明抛出异常

  • 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这 种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理, 而由该方法的调用者负责处理。
  • 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类

声明抛出异常举例:

public void readFile(String file) throws FileNotFoundException {
        ……
        // 读文件的操作可能产生FileNotFoundException类型的异常
        FileInputStream fis = new FileInputStream(file);
        ..……
}

重写方法声明抛出异常的原则

  • 重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下,对methodA()方法的调用-异常的捕获按父类声明的异常处理。

                                ​​​​​​​

开发中如何选择使用try-catch-finally 还是使用throws?

  • 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
  • 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。

​​​​​​​手动抛出异常 

在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的方式来告诉调用者。在java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。那么,抛出一个异常具体如何操作呢?

  1. 创建一个异常对象。封装一些提示信息(信息可以自己编写)。
  2. 需要将这个异常对象告知给调用者。怎么告知呢?

怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。

使用格式: 

学习完抛出异常的格式后,我们通过下面程序演示下throw的使用

package demo01;
 
 
/*
    throw关键字
    作用:
        可以使用throw关键字在指定的方法中抛出指定的异常
    使用格式:
        throw new xxxException("异常产生的原因");
    注意:
        1.throw关键字必须写在方法的内部
        2.throw关键字后边new的对象必须是Exception或者Exception的子类对象
        3.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
            throw关键字后边创建的是RuntimeException或者是 RuntimeException的子类对象,
我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
            throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch
 */
public class Demo03Throw {
    public static void main(String[] args) {
        //int[] arr = null;
        int[] arr = new int[3];
        int e = getElement(arr, 3);
        System.out.println(e);
    }
 
    /*
        定义一个方法,获取数组指定索引处的元素
        参数:
            int[] arr
            int index
        以后(工作中)我们首先必须对方法传递过来的参数进行合法性校验
        如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者,传递的参数有问题
        注意:
            NullPointerException是一个运行期异常,我们不用处理,默认交给JVM处理
            ArrayIndexOutOfBoundsException是一个运行期异常,我们不用处理,默认交给JVM处理
     */
    public static int getElement(int[] arr, int index) {
        /*
            我们可以对传递过来的参数数组,进行合法性校验
            如果数组arr的值是null
            那么我们就抛出空指针异常,告知方法的调用者"传递的数组的值是null"
         */
        if (arr == null) {
            throw new NullPointerException("传递的数组的值是null");
        }
 
        /*
            我们可以对传递过来的参数index进行合法性校验
            如果index的范围不在数组的索引范围内
            那么我们就抛出数组索引越界异常,告知方法的调用者"传递的索引超出了数组的使用范围"
         */
        if (index < 0 || index > arr.length - 1) {
            throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围");
        }
 
        int ele = arr[index];
        return ele;
    }
}

自定义异常 

为什么需要自定义异常类:?

我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN没有定义好的,此时我们根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题等等。在上述代码中,发现这些异常都是JDK内部定义好的,但是实际开发中也会出现很多异常,这些异常很可能在JDK中没有定义过,例如年龄负数问题,考试成绩负数问题.那么能不能自己定义异常呢? 

什么是自定义异常类:

  • 在开发中根据自己业务的异常情况来定义异常类. 

异常类如何定义:

  • 自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。
  • 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。 

练习

要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。

首先定义一个登陆异常类RegisterException: 

package demo02;
 
/*
    自定义异常类:
        java提供的异常类,不够我们使用,需要自己定义一些异常类
    格式:
        public class XXXExcepiton extends Exception | RuntimeException{
            添加一个空参数的构造方法
            添加一个带异常信息的构造方法
        }
     注意:
        1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类
        2.自定义异常类,必须的继承Exception或者RuntimeException
            继承Exception:那么自定义的异常类就是一个编译期异常,
如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch
            继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
 */
public class RegisterException extends /*Exception*/ RuntimeException {
    //添加一个空参数的构造方法
    public RegisterException() {
        super();
    }
 
    /*
        添加一个带异常信息的构造方法
        查看源码发现,所有的异常类都会有一个带异常信息的构造方法,
        方法内部会调用父类带异常信息的构造方法,
        让父类来处理这个异常信息
     */
    public RegisterException(String message) {
        super(message);
    }
}

模拟登陆操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否存在方法用于判断。 

package demo02;
 
 
import java.util.Scanner;
 
/*
    要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
    分析:
        1.使用数组保存已经注册过的用户名(数据库)
        2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        3.定义一个方法,对用户输入的中注册的用户名进行判断
            遍历存储已经注册过用户名的数组,获取每一个用户名
            使用获取到的用户名和用户输入的用户名比较
                true:
                    用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";
                false:
                    继续遍历比较
            如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!";
 */
public class Demo02RegisterException {
    // 1.使用数组保存已经注册过的用户名(数据库)
    static String[] usernames = {"张三","李四","王五"};
 
    public static void main(String[] args) {
        //2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要注册的用户名:");
        String username = sc.next();
        checkUsername(username);
 
    }
 
    //3.定义一个方法,对用户输入的中注册的用户名进行判断
    public static void checkUsername(String username)  {
        //遍历存储已经注册过用户名的数组,获取每一个用户名
        for (String name : usernames) {
            //使用获取到的用户名和用户输入的用户名比较
            if(name.equals(username)){
                //true:用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";
                throw new RegisterException("亲,该用户名已经被注册");//抛出运行期异常,无需处理,交给JVM处理,中断处理
            }
        }
 
        //如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!";
        System.out.println("恭喜您,注册成功!");
    }
}

总结:异常处理5个关键字

           

记住一下几点

  • 异常处理要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。 
  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。 
  • 如果finally有return语句,永远返回finally中的结果,避免该情况. 
  • 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
  • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出 
发布了20 篇原创文章 · 获赞 7 · 访问量 6612

猜你喜欢

转载自blog.csdn.net/weixin_44462792/article/details/105062827