Java异常的使用

本文介绍了Java中的异常处理,包括异常的概念、体系结构,如Throwable、Error和Exception的区别。详细讲解了运行时异常和编译时异常,以及如何使用throws关键字声明异常,throw关键字抛出异常,try-catch语句块捕获并处理异常,finally语句块用于确保资源释放。此外,文章还讨论了自定义异常的创建和使用,强调了异常处理在编程中的重要性。
摘要由CSDN通过智能技术生成

异常的使用

1.异常的概念

什么是异常呢?我相信大家在写代码的时候,会经常遇见这个东西,这个东西长什么样呢?看下面这几张图:

image-20230409145517207

image-20230409145619219

image-20230409145635676

想必大家看到这几张图,就明白了什么是异常,我相信大家或多或少都有见到过这些东西,在Java中,程序执行的时候发生不正常的行为就称为异常,那你看看这个是不是异常:

image-20230409145830146

这个就不是异常了,这个就是个错误了,遇见了这种错误,就说明你代码写的是有问题的,才会导致这种错误,一般遇到这种错误,程序就直接挂掉了,就不可能往后执行了,那异常呢?如果我们没有对他进行任何的处理,程序最后也会挂掉。所以我们得理解一下异常的体系和处理异常的方法。

2.异常的体系

在Java中异常有很多很多种,我们看看下面这张图,来了解一下异常的体系划分。

Throwable

从图中可以分析出这么几点:

  1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception。
  2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError,一旦发生程序就挂掉了。
  3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。我们平时所说的异常就是Exception。

3.异常的处理机制

异常类型

首先异常也分为两种:

  1. 运行时异常也称为非受查异常
  2. 编译时异常也称为受查异常

我写几个异常的案例让大家看看,大家就能明白异常的种类了:

运行时异常有这些:

  • ArithmeticException 算术异常

    public static void main(String[] args) {
        //这个是算数异常ArithmeticException
        System.out.println(10 / 0);
    }
    
  • NullPointerException 空指针异常

    public static void main(String[] args) {
        int[] arr = null;
        //这个是空指针异常NullPointerException
        System.out.println(arr.length);
    }
    
  • ArrayIndexOutOfBoundsException 数组下标越界异常

    public static void main(String[] args) {
        int[] arr = {1,2,3};
        //ArrayIndexOutOfBoundsException数组下标越界异常
        System.out.println(arr[100]);
    }
    

编译时异常有这些:

  • CloneNotSupportedException克隆异常

    class Student implements Cloneable{
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    public class Test {
       
        public static void main(String[] args) {
            Student s1 = new Student();
            /**
             * 大家看这行代码,他为什么报红了,因为他调用了clone方法,里面抛出了一个异常叫做
             * CloneNotSupportedException,这种异常就需要大家手动处理掉,如果你不处理,你的
             * 代码就编译不通过,这里我就要给大家介绍一个新的关键字叫做throws,把你的鼠标放到
             * 报错的位置上,按住alt+enter键,选择第一个选项,你会发现你的代码不报错了,为什
             * 么呢?你会看到你的main方法后面多了一串字符叫做
             * throws CloneNotSupportedException,这是用来干什么的呢?下面重点介绍
             */
            Student s2 = (Student)s1.clone();
        }
    }
    

throws关键字的使用

在上面案例中,我们能看到加了throws,代码就没有报错了,这是为什么呢?我们先聊聊throws的作用。

throws的作用:用来声明一个异常,具体有什么用呢?举个例子,比如说我在写方法的时候,碰见了一个异常,但是我自己又不想处理,那这个时候就可以在方法的参数列表之后加一个throws,来声明这个异常,那这个时候,你就可以不用自己处理异常了,他就会交给方法的调用者来处理,如果这个方法的调用者也不想处理呢?也可以加throws来声明这个异常,那这个时候异常就会交给JVM来处理,那你的程序就会直接结束。也就是说如果你们都不想处理,那JVM就会替你们兜底。

public class Test {

    //这里我创建了一个方法,里面抛出了一个受查异常也就是编译时的异常
    public static void test()throws CloneNotSupportedException{
        /**
         * 在我抛出这个异常的时候,你会发现这还代码报红了,因为这个异常需要手动处理,
         * 如果我不想处理怎么办,我想交给调用这个函数的人来处理,那你就在方法后面
         * 加上throws关键字,来声明这个异常,这个代码就不会报错,相当于踢皮球,把
         * 问题交给别人处理。
         */
        //这里使用了throw关键字来抛出一个异常
        throw new CloneNotSupportedException();
    }

    public static void main(String[] args)throws CloneNotSupportedException {
        /**
         * 你看在main方法这里,我调用test()方法,会直接报错,因为这个方法抛出的异常
         * 还没有解决,所以他就在这里报错,如果我也不想处理掉这个异常该怎么办,那我也可以
         * 在方法后面加上throws,来声明这个异常,那这个时候,异常就会交给JVM去处理,
         * 但是不是说你加了这个关键字,你写的就是对的,JVM在看到这个异常的时候,会直接把程序结
         * 束掉。我主要是想通过这个案例来讲解throws关键字怎么使用。
         */
        test();
    }
}

throws需要注意的点

  1. throws必须跟在方法的参数列表之后。

  2. 声明的异常必须是 Exception 或者 Exception 的子类。

  3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。

throw关键字的使用

大家区分清楚哈!总有人会把这两个关键字搞混,一个是加了s,一个没有加,那throw关键字的作用是什么呢?

throw的作用:用来抛出一个异常,通常是用来抛出我们自定义的异常。

public class Test {
    
    public static void test2(){
        //通过throw关键字来抛出一个异常
        throw new ArithmeticException();
    }

    public static void main(String[] args) {
        /**
         * 这个时候调用test2()方法,代码会直接报错,那如果想处理掉该怎么办,
         * 这时候就要用到try catch了
         * try catch语法:
         * try {
         *    这里写有问题的语句
         * } catch (Exception e) {
         *   这里写对有问题的语句进行处理的逻辑
         * }
         */
        try {
            test2();
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException");
            e.printStackTrace();
        }
        //这里你会发现,通过try catch这种方法处理掉的异常,后面的代码竟然会正常执行
        System.out.println("aaaa");
    }
}

throw需要注意的点:

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

try catch语句块的使用

首先在方法后面加上throws并没有真正的解决掉这个异常,而是把这个异常交给别人去处理了,要想真正的处理好异常,还得靠try catch。

try catch的作用:这个语句块就是专门用来处理异常的,try语句块里面填写有问题的语句,catch语句块是负责捕获这些异常,比如说我try语句块里面抛出了一个受查异常,catch语句块刚好捕捉到了,那就可以在catch语句块里面写处理这个异常语句的逻辑。

public static void test2(){
    //通过throw关键字来抛出一个异常
    throw new ArithmeticException();
}

public static void main(String[] args) {
    /**
     * try catch语法:
     * try {
     *    这里写有问题的语句
     * } catch (Exception e) {
     *   这里写对有问题的语句进行处理的逻辑
     * }
     */
    try {
        //这里存放会抛出异常的语句
        //如果test2()这个方法抛出了一个异常,那么他后面的代码将不会执行
        test2();
        //这条语句不会被执行
        System.out.println("bbb");
    }catch (ArithmeticException e){
        //这里写对这个异常处理的语句
        System.out.println("算术异常");
        e.printStackTrace();
    }

    //通过try catch处理的异常,后面的语句会正常执行
    System.out.println("aaa");
}

通过上面例子我们能很直观的看出,真正要处理异常的话,还得靠try catch,那他里面有哪些需要注意的点呢?

try catch需要注意的点:

  1. try语句块里面不一定会抛出异常。

    public static void main(String[] args) {
        try {
            //这里面不一定会抛出异常,写正常代码也可以
            int a = 10;
            int b = 20;
            System.out.println(a + b);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
  2. 如果要处理多个异常的话,catch可以写多次。

    public static void main(String[] args) {
        try {
            //算术异常
            System.out.println(1 / 0);
    
            int[] arr = null;
            //空指针异常
            System.out.println(arr.length);
    
            //数组下标越界异常
            arr = new int[]{1, 2, 3};
            System.out.println(arr[100]);
            /**
             * 如果要处理多个异常,catch也可以写多个,处理异常的顺序是根据try里面的代码,
             * 由上往下执行,谁先抛出异常,就会先处理那个异常,和catch写的顺序无关,并且
             * 同一时间只会抛出一个异常,不会同时抛出多个,catch如果捕获到
             * 了这个异常,就会进入对应的catch语句块里面,如果你抛出的这个异常,catch里
             * 面没有对应的捕获代码,那最后这个异常就会交给JVM去处理,JVM就会把你的程序结束掉。
             */
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }
    
  3. try语句块里面抛出异常之后的代码将不会被执行。

    public static void main(String[] args) {
        try {
            //算术异常
            System.out.println(1 / 0);
            /**
             * 这一点刚刚有讲过,就是说如果上面的代码已经抛出异常了,那你后面的代码
             * 都不会被执行。
             */
            System.out.println("aaaa");
        }catch (ArithmeticException e){
            e.printStackTrace();
        }
        //try catch处理完异常后,后面的代码一定会被执行到
        System.out.println("bbbbbbbbb");
    }
    
  4. 如果说异常之间具有父子类关系的话,一定要把子类的异常放在最前面,父类的异常放在最后面,永远不要想着一劳永逸的写法哈!不要想着嫌麻烦,就把Exception写在最前面了,那样谁知道这个程序报的是什么异常呢?

    public static void main(String[] args) {
        //这种写法有问题哈!不要这么写代码
        //error写法
        //try {
            //System.out.println(10 / 0);
            /**
             * 这个写法很有问题,Exception是所有异常的父类,那也就是说你放到catch的最前面
             * 那后面的异常就不可能被捕获到了,所以代码就出错了,永远不要嫌麻烦,想着一劳
             * 永逸的写法,正确做法是把Exception异常放到最后,如果前面的异常都没有捕获到
             * 那还有Exception替你兜底。
             *
             */
        //}catch (Exception e){
         //   e.printStackTrace();
        //}catch (ArithmeticException e){
         //   e.printStackTrace();
        //}
    
        //正确写法:
        try {
            System.out.println(10 / 0);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("bbb");
    }
    
  5. 如果你的try语句块里面没有受查异常的话,但是你却在catch语句块里面写了一个捕获受查异常的代码,那你的程序一定会报错。这种写法是错误的。所以不要这么写代码哈!

    public static void main(String[] args) {
        try {
            System.out.println(10 / 0);
            /**
             * 这里为什么报错了呢?我们的受查异常是在编译的时候
             * 就会被编译器检测到,如果你在try语句块中没有写受查异常
             * 相关的语句,但是你却在catch语句块里面写了捕获受查异常相关的语句,
             * 那么这个时候,你的代码就会报错了,因为你都没有抛出受查异常,
             * 那你想让catch帮你捕捉什么呢?所以这种写法也是有问题的。
             */
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
    

finally语句块的使用

我们经常会看到别人的代码,就是在try catch后面会常常加着一个finally,那这个finally有什么作用呢?

finally的作用:这个语句块通常是用来关闭我们的连接资源,比如说数据库,文件操作阿!这些连接资源你用完之后,要记着及时关闭,否则会导致内存泄露。finally语句块的特点就是,无论程序如何执行,最后一定会执行finally语句块里面的语句。

  1. 释放连接资源

    public static void main(String[] args) {
        /**
         * 大家看这个代码,我拿到创建了一个Scanner对象,最后就是
         * 通过finally代码,来释放Scanner这个连接资源,大家写代码
         * 的时候,记着就是用完资源之后,要及时释放掉。
         */
        //这是第一种释放的方式,还可以采用简写
        Scanner scanner = new Scanner(System.in);
        int a = 0;
        try {
            a = scanner.nextInt();
        } catch (ArithmeticException e) {
            System.out.println("b");
        } finally {
            scanner.close();
        }
    
        //也可以通过这种方式,来释放我们的连接资源,两种方式都可以
        try (Scanner scanner2 = new Scanner(System.in)) {
            a = 0;
            a = scanner2.nextInt();
        } catch (ArithmeticException e) {
            System.out.println("b");
        }
    }
    
  2. 代码即使抛出了异常,finally语句块也会被执行到

    public static void main(String[] args) {
        try {
            System.out.println(10 / 0);
            /**
             * 大家看这里没有写catch语句块,也就说这里肯定是捕获不到这个异常,
             * 那就会把这个异常交给JVM来处理,那你的程序就会被结束掉。
             * 但是这个代码还是会执行finally语句块里面的内容。
             */
        } finally {
            System.out.println("finally里面的语句");
        }
        /**
         * 你看程序是不是直接结束掉了,并且外面的代码没有执行到
         * 但是finally里面的代码还是被执行了,这就印证了我说的那点
         * finally里面的内容最后一定会被执行
         */
        System.out.println("外面的语句");
    }
    
  3. 还有更极端一点的情况,比如说这种代码。

    /**
     * 大家看这里我写了个方法,这个方法有返回值,但是会感觉到很奇怪,为什么有三个return呢?
     * 那到底返回哪一个值呢?正常的思考逻辑,执行到try语句块里面的时候,碰见return了,那这个时候
     * 方法就应该已经结束了,然后把1给return回去,但是呢?这个代码里面有finally,我说过finally里面
     * 的代码一定会被执行到,那现在是个什么逻辑呢?同学是这样的,try里面有return并且finally里面也有
     * return的时候,那这个时候,代码不会执行try里面的return,而是去执行finally里面的return,然后
     * 这个时候就会把3给return出去了,所以看到的结果就是3。大家记住在公司的时候,你是不会遇到这种代码
     * 的,这种代码多半是面试题,就是面试官特意来考你的,所以大家不要思考错了方向。
     */
    public static int test() {
        try {
            return 1;
        } catch (ArithmeticException e) {
            return 2;
        } finally {
            return 3;
        }
    }
    
    public static void main(String[] args) {
        System.out.println(test());
    }
    

以上就是finally所需要特别注意的一些点。

总结:想学好异常,那就先要把这5个关键字给弄懂。再给大家总结一下这5个关键字的作用是什么。

throws:用来申明一个异常,一般放在方法参数列表后面,花括号的前面。只能抛出Exception和Exception子类的异常。

throw:用来抛出一个异常,一般放在方法体里面。只能抛出Exception和Exception子类的异常。

try:用来存放有异常的语句。当然没有异常的语句也可以往里放。

catch:用来捕获一个异常。如果有多个异常的时候,可以写多个catch,如果异常里面有父子类关系的话,把子类放在前面,父类放在后面。

finally:用来释放各种连接资源。通常放在catch语句块之后,也可以放在try语句块之后。这个语句块的特点就是,无论程序如何执行,最后一定会执行里面的代码。

4.自定义异常

大家想一想,Java里面虽然提供了很多异常给我们,但是这里面一定能包含我想要的异常吗?不一定吧!如果我们想自己定义一个异常该怎么办呢?大家看下面这个案例:

//现在我有如下代码,这个代码没有用到任何异常的相关内容
public class Test {
    //用户名
    public static final String userName = "ZhangSan";
    //密码
    public static final String passWord = "123456";

    public static boolean login(String username, String password) {
        if (!userName.equals(username)) {
            System.out.println("用户名错误");
            return false;
        }

        if (!passWord.equals(password)) {
            System.out.println("密码错误");
            return false;
        }
        return true;
    }

    public static void main(String[] args) {
        /**
         * 现在我要登录了,我随便输入,他提示我登录失败,我感觉就是文字
         * 给我的消息可能不够,他能不能告诉我是哪一行代码有问题呢?
         * 哪一个输错了呢?这个时候我们就要采用异常的方式来处理
         */
        String username = "aaa";
        String password = "bbb";
        if (login(username, password)) {
            System.out.println("登录成功");
        } else {
            System.out.println("登录失败");
        }
    }
}

采用自定义异常的方式来处理:

/**
 * 现在我们来看看如果自定义一个异常:
 * 1.首先创建一个类,因为在之前分析的时候,我们会发现所有的异常其实就是一个类
 * 2.这个类要继承Exception,因为我们之前说过,如果想用throw去抛出一个异常
 * ,那这个异常必须是Exception或者是Exception的子类,所以要继承Exception
 * 3.用throw去抛出这个异常
 */

//创建一个类,需要继承Exception或者是Exception的子类
class UserException extends RuntimeException {
    //这里面还可以写构造方法,帮助我父类进行初始化

    public UserException() {
        super();
    }

    //这里我们能了解到父类里面有个成员属性message,如果我给里面赋值会发生什么呢
    public UserException(String message) {
        super(message);
    }

}

//创建一个类,需要继承Exception或者是Exception的子类
class PassWordException extends RuntimeException {
    //这里面还可以写构造方法,帮助我父类进行初始化

    public PassWordException() {
        super();
    }

    //这里我们能了解到父类里面有个成员属性message,如果我给里面赋值会发生什么呢
    public PassWordException(String message) {
        super(message);
    }
}


//现在我有如下代码,这个代码正在用异常的相关内容
public class Test2 {
    //用户名
    public static final String userName = "ZhangSan";
    //密码
    public static final String passWord = "123456";


    public static void login(String username, String password) {
        if (!userName.equals(username)) {
            //通过throw去抛出我们自定义的异常
            /**
             * 现在爆红是因为我们自定义的异常是一个受查异常,但是这个异常不符合我们的逻辑,
             * 那我们可以让他继承RuntimeException,变成一个运行时的异常,这个时候就不会报错了
             */
            
            //没有赋值的构造方法,里面没有任何参数的构造方法,只会打印你错在哪一行了
            //throw new UserException();

            //赋值的构造方法,这种加了参数的构造方法,可以把你自己想说的信息写在里面
            throw new UserException("大兄弟,你的用户名错啦!");

        }

        if (!passWord.equals(password)) {
            //通过throw去抛出我们自定义的异常

            //没有赋值的构造方法,里面没有任何参数的构造方法,只会打印你错在哪一行了
            //throw new PassWordException();

            //赋值的构造方法,这种加了参数的构造方法,可以把你自己想说的信息写在里面
            throw new PassWordException("大兄弟,你的密码错啦!");
        }
    }

    public static void main(String[] args) {
        String username = "aaa";
        String password = "111";
        /**
         * 现在异常已经定义好了,我们运行看看效果,这个时候就达到我想要的效果了,
         * 编译器通过错误提示,告诉我错在哪里了。
         */
        login(username, password);
        System.out.println("登录成功");
    }
}

image-20230410160554459

通过上面的案例,我们能了解到自定义的异常的步骤,并且了解到异常的好处,通过异常提供的消息,我们能很快的发现,我们的错误错在哪里了,这里再给大家总结一下,自定义异常的步骤:

  1. 首先创建一个类,因为在之前分析的时候,我们会发现所有的异常其实就是一个类。
  2. 这个类必须继承Exception或者是Exception的子类,因为我们之前说过,如果想用throw去抛出一个异常,那这个异常必须是Exception或者是Exception的子类,所以我们要继承Exception,变成Exception的子类才能被抛出去。
  3. 我们继承Exception类之后,可以创建两个构造方法,一个带参数的,一个不带参数,主要来了解一下这个带参数的方法,这个参数是个String类型,我们可以把我们想要说的话,通过参数传递过去,这样你就能在控制台上看到你所写的相关信息,对程序员更友好一点,程序员一看到这个信息,立马就知道自己错在哪里了。
  4. 用throw去抛出这个异常。
  5. 如果继承的是Exception的话,默认是一个受查异常,如果继承的是RuntimeException的话,默认是一个运行时异常。

5.致谢

感谢你一路看到这里,如果你觉得我写的不错的话,请给我一键三连!!!😉😉

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值