JavaSE入门---关于异常那些事儿

文章目录

  • 什么是异常?
  • 异常的体系结构
  • 异常的分类
    • 一个异常的完整执行流程
  • 异常的处理
    • 防御式编程
    • 异常的声明
    • 异常的捕获
      • 异常声明throws
      • try-catch捕获并处理
      • finally
    • 异常的处理流程
  • 自定义异常类

什么是异常?

在Java中,将程序执行过程中出现的不正常行为称为异常。比如:

		//算术异常
        System.out.println(10/0);
        //输出:Exception in thread "main" java.lang.ArithmeticException: / by zero

        //数组越界异常
        int[] arr = {1,2,3};
        System.out.println(arr[10]);
        //输出:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10

        //空指针异常
        int[] array = null;
        System.out.println(array.length);
        //输出:Exception in thread "main" java.lang.NullPointerException

异常的体系结构

异常在Java中以类的形式出现,每一个异常类都可以创建异常对象。异常种类繁多,为了对不同异常或者错误进行很好的分类管理,Java内部维护了一个异常的体系结构:

JavaSE入门---关于异常那些事儿_第1张图片

注:

  1. throwable是异常体系的顶层类,其派生出两个重要子类error类和exception类;
  2. error类:指JVM无法解决的严重问题,如:JVM的内部错误、资源耗尽等;
  3. exception类:指异常产生后程序员可以通过代码进行处理,使程序继续执行的问题;

异常的分类

可以将异常分为:

  1. 编译时异常,也称为 受检查异常,要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错;
    编译时异常是在编译阶段发生的吗?
    不是。是在编写程序的时候发生
  2. 运行时异常,也称为 非受检查异常,在编写程序阶段程序员可以预先处理,也可以不管,都行。;

JavaSE入门---关于异常那些事儿_第2张图片

注:

  1. 异常分为编译时异常和运行时异常,但所有异常都是在运行阶段发生的,因为,只有运行阶段才可以new对象,异常的发生就是new异常对象。
  2. 编译时异常一般发生的概率比较高,需要在运行之前进行预处理;运行时异常一般发生的概率比较低,在运行之前不必进行预处理。

一个异常的完整执行流程

	 public static void main(String[] args) {
        System.out.println(10/0);

        //这里的hello world 没有执行
        System.out.println("hello world");
    }

流程:

  1. 程序执行到System.out.println(10/0),发生了ArithmeticException算术异常,底层new了一个ArithmeticException异常对象,然后抛出了
  2. 由于是main方法调用了10/0,所以这个ArithmeticException抛给了main方法,但main方法没有处理,又会把这个异常抛给JVM
  3. JVM中止程序的执行,此时,System.out.println(“hello world”)不会执行。

异常的处理

防御式编程

  • LAYL:Look Before You Leap,操作之前就做充分的检查,即事前防御型
boolean ret = false;
        ret = 登录游戏();
        if (!ret){
            处理登录游戏异常;
            return;
        }

        ret = 开始匹配();
        if (!ret){
            处理开始匹配异常;
            return;
        }

        ret = 游戏确认();
        if (!ret){
            处理游戏确认异常;
            return;
        }

        ret = 载入游戏画面();
        if (!ret){
            处理载入游戏画面异常;
            return;
        }
        .....

缺点:正常代码和错误代码流程放在一起,代码整体显的比较混乱。

  • EAFP:It‘s Easier to Ask Forgiveness than Permission 事后获取原谅比事前获取许可更容易,也就是先操作,遇到问题再处理;即事后认错型
	try {
            登陆游戏();
            开始匹配();
            游戏确认();
            载入游戏画面();
            ...
        } catch (登陆游戏异常) {
            处理登陆游戏异常;
        } catch (开始匹配异常) {
            处理开始匹配异常;
        } catch (游戏确认异常) {
            处理游戏确认异常;
        } catch (载入游戏画面异常) {
            处理载入游戏画面异常;
        }
        ......

优势:正常流程和错误流程是分离开的,程序员更关注正常流程,代码更清晰,更容易理解。

所以,我们在处理异常时的核心思想就是 EAFP,我们需要明白“异常一旦出现是必须要被处理了才能继续执行后面的代码的”,处理异常可以交给JVM处理:直接中止程序执行;交给用户自己处理:可以按照自己的意愿来进行合适的处理,再执行后面的代码;

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

异常的声明

在编写程序时,如果程序中出现错误,此时就需要把错误的信息告诉调用者;

在Java中,可以通过throw关键字,来声明一个指定的异常对象,将错误的信息告诉调用者。但是它不会处理异常,必须搭配throws或者try-catch来使用。

用throw声明异常后会自动抛出

由方法的调用者使用throws配合使用:上报给上一级,直到交给JVM处理程序直接中止,不会执行后面的代码;
由方法的调用者使用try-catch配合使用:用户自己处理,处理之后可以继续执行后面的代码;

	public static int getElement(int[] arr,int index){
        if (arr == null){
            //通过throw 告诉调用者出现的异常情况信息
            throw new NullPointerException("传递的数组为空");
        }

        if (index < 0 || index >= arr.length){
            //通过throw 告诉调用者出现的异常情况信息
            throw new ArrayIndexOutOfBoundsException("要访问的数组下标越界");
        }
        
        return arr[index];
    }

    //实现一个获取数组指定位置元素的方法
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        int ret = getElement(arr,2);
        System.out.println(ret);
    }

注:

  1. throw必须写在方法体的内部;
  2. 抛出的对象必须是Exception或者Exception的子类对象;
  3. 如果抛出编译时异常,用户必须处理,否则无法通过编译;
  4. 如果抛出运行时异常,用户可以不处理,交给JVM处理;
  5. 异常一旦抛出,其后的代码就不会执行;

异常的捕获

异常的捕获也就是异常的具体处理方式,有两种:异常声明throws和try-catch异常捕获;

异常声明throws

当方法中抛出编译时异常(使用throws抛出异常后必须有人来进行处理),用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者处理。即当前方法不处理异常,提醒方法的调用者处理异常,如果方法的调用者也不处理,则会交给JVM处理,程序直接中止运行

  • 方法抛出异常后,方法的调用者不处理,继续向上级抛出异常,直到让JVM处理:程序中止。不执行后面的代码。
	public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
        return arr[index];
    }
    public static void main(String[] args) throws ArrayIndexOutOfBoundsException{
        int[] arr = {1,2,3};
        int ret = getElement(arr,3);

        System.out.println("执行后面的代码");
    }

//会打印出异常日志   但不会打印出   执行后面的代码
  • 方法抛出异常后,方法的调用者使用try-catch处理异常。会执行后面的代码。
	public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
        return arr[index];
    }
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        try {
            int ret = getElement(arr,3);
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }

        System.out.println("执行后面的代码");
    }

//会打印出异常日志 和 执行后面的代码

注:

  1. 只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。就像上面说的第一种解决方法
  2. try-catch捕捉异常后,后续代码可以执行。就像上面说的第二种解决方法。
  3. try语句块中的某一行出现异常,该行后面的代码不会执行
public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
        return arr[index];
    }
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        try {
            int ret = getElement(arr,3);
            //下面的代码不会执行
            System.out.println("getElement之后的语句");
            System.out.println("getElement之后的语句");
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }

        //这里的代码还会执行
        System.out.println("执行后面的代码");
    }

//会打印出异常日志 和 执行后面的代码

try-catch捕获并处理

throws并没有对异常进行真正的处理,而是将异常抛出给方法的调用者,由调用者处理;如果真正要对异常进行处理,就需要try-catch。

	public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
        return arr[index];
    }
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        try {
            int ret = getElement(arr,3);
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }

        System.out.println("执行后面的代码");
    }

注:

  1. catch()里的 e ,表示是e引用,保存的地址是那个new出来的异常对象的地址;
  2. try中可能会抛出多个异常,则建议使用多个catch来一对一精确捕获;
  3. catch中捕获的异常类型既可以是具体的异常类型,也可以是该异常类型的父类型。
  4. catch中捕获多个异常时,从上到下,必须遵守异常的范围从小到大。否则先捕获到该类的父类型时,就不会继续向下检测,不会捕获到具体的异常类型了。
ArrayIndexOutOfBoundsException,NullPointerException{
        return arr[index];
    }

    public static void main(String[] args) {
        int[] arr = {1,2,3};
        try {
            int ret = getElement(arr,1);
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }catch (NullPointerException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
    } ```

5. catch捕获多个异常时,异常间可以用 | 分割

```java 	public static int getElement(int[] arr, int index) throws
ArrayIndexOutOfBoundsException,NullPointerException{
        return arr[index];
    }

    public static void main(String[] args) {
        int[] arr = {1,2,3};
        try {
            int ret = getElement(arr,1);
        }catch (ArrayIndexOutOfBoundsException  | NullPointerException | Exception){
            System.out.println("出现异常");
        }
    } ```

finally

在某些特定的代码中,我们必须要求其释放持有的资源,比如文件读取等,但是当遇到异常时,它下面的一些代码就不会被执行到,这样就会引发一些问题。因此,我们使用finall来解决这个问题。

finall子句必须和try一起出现,不能单独编写。
即使try语句块中的代码出现了异常,finall中的代码也是一定会执行的。

	public static void main(String[] args) {
        try {
            System.out.println("try");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("finally");
        }
    }

注:

  1. try和finally联用,没有catch;
        try {
            System.out.println("try");
            return;
        } finally {
            System.out.println("finally");
        }
    } ```

2. 如果try语句块中有return,那么tryfinallyreturn的执行顺序是怎样的?

   先执行try、再执行finally、最后执行returnfinally执行的时机是在return以前。

3. 如何让finally失效呢? System.exit()

```	public static void main(String[] args) {
        try {
            System.out.println("try");
            //退出JVM 退出之后finally中的语句就不执行了
            System.exit(0);
        } finally {
            System.out.println("finally");
        }
    } ```

4. 一般在finally中进行一些资源清理的工作。

异常的处理流程

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

自定义异常类

Java中,虽然已经内置了丰富的异常类,但是并不能完全表示在实际开发中遇到的所有异常,此时就需要我们自己定义异常。

自定义异常有两步:

  1. 编写一个类,继承Exception或者RuntimeException。
  2. 提供两个构造方法,一个无参,一个带有String参数。

下面以登录为例,演示自定义异常类的使用。

//自定义用户名错误类
class UsernameFalseException extends Exception{
    public UsernameFalseException(){

    }

    public UsernameFalseException(String msg){
        super(msg);
        System.out.println(msg);
    }
}

//自定义密码错误类
class PasswordFalseException extends Exception{
    public PasswordFalseException(){

    }

    public PasswordFalseException(String msg){
        super(msg);
        System.out.println(msg);
    }
}

public class Test {
    private static String USER_NAME = "admin";
    private static String PASS_WORD = "123456";


    public static void login(String username,String password) throws UsernameFalseException, PasswordFalseException {
        if (!username.equals(USER_NAME)){
            throw new UsernameFalseException("用户名错误");
        }

        if (!password.equals(PASS_WORD)){
            throw new PasswordFalseException("密码错误");
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String username = scanner.nextLine();
        String password = scanner.nextLine();

        try {
            login(username,password);
            System.out.println("登录成功");
        }catch (UsernameFalseException e){
            e.printStackTrace();
        }catch (PasswordFalseException e){
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(JavaSE,开发语言,java)