Java异常处理--自定义异常类及课后练习

发布时间:2024年01月18日

一、自定义异常

(1)为什么需要自定义异常类

throw抛出异常对象,之前是使用API提供好的,比如Exception、RuntimeException等。在实际开发当中,还可以自定义异常,然后在抛的时候就可以使用自定义类的对象。

Java中不同的异常类,分别表示着某一种具体的异常情况。那么在开发中总是有些异常情况是核心类库中没有定义好的,此时我们需要根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题,某员工已在团队中等。

我们其实更关心的是,通过异常的名称就能直接判断此异常出现的原因

既然如此,我们就有必要在实际开发场景中,不满足我们指定的条件时,指明我们自己特有的异常类。通过此异常类的名称,就能判断出具体出现的问题。

比如:

//2、手动抛出异常类的对象--普通异常 (非运行时异常)
//throw new Exception("输入的id非法");
//System.out.println("此语句不能被执行");

//3.自定义异常
throw new BelowZeroException("输入的id非法");

(2)自定义异常类的定义及使用

1、如何自定义异常类

(1)要继承一个异常类型

  • 自定义一个编译时异常类型:自定义类继承java.lang.Exception
  • 自定义一个运行时异常类型:自定义类继承java.lang.RuntimeException

(2)建议大家提供至少两个构造器,一个是无参构造,一个是(String message)构造器。

(3)自定义异常需要提供serialVersionUID

比如这个例子中:

package yuyi02;

public class ThrowTest {
    public static void main(String[] args) {
        Student s1=new Student();

        //手动抛出异常类的对象--普通异常 (非运行时异常)
        try {
            s1.regist(-10);
            System.out.println(s1);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

class Student{
    int id;

    public void regist(int id) throws Exception{
        if(id>0){
            this.id=id;
        }else{
            //手动抛出异常类的对象--普通异常 (非运行时异常)
            throw new Exception("输入的id非法");

        }
    }

    @Override
    public String toString() {
        return "Student{" +
        "id=" + id +
        '}';
    }
}

在这个例子中,若是输入的id是负数,那么就会抛出一个异常对象。

不妨我们就造一个自定义的异常,比如BelowZeroException

public class BelowZeroException {

}

现在可以抛这个异常吗?

明显是不可以的,如下:

image.png

为啥不让抛?这是因为throw后面需要跟的是“异常类的对象”。

现在的BelowZeroException明显不是异常类。

这就好比这样的情况,看着可以,其实完全不行(后面跟的不是异常类的对象),如下:

image.png

那怎样才可以?需要让自定义类继承于异常体系,那又继承于谁呢?那就要看你想要什么异常了,比如想要运行时异常,那就继承于RuntimeException

image.png

现在就好使了,BelowZeroExceptionRuntimeException体系下的一个对象,就是一个异常对象,没有问题。

此时是运行时异常,就可以考虑不做处理,因此这样即可:

class Student{
    int id;

    public void regist(int id) {
        if(id>0){
            this.id=id;
        }else{
            //自定义异常
            throw new BelowZeroException();
        }
    }
}

若此时继承的是Exception,是非运行时异常,就需要处理一下了,如下:

image.png


所以,自定义类要继承于现有的异常体系,可以继承于RuntimeException或者Exception

接下来怎么做呢?

可以进入Exception里面看一下:

public class Exception extends Throwable {
    @java.io.Serial
    static final long serialVersionUID = -3387516993124229948L;

    public Exception() {
        super();
    }

    public Exception(String message) {
        super(message);
    }

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

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

    protected Exception(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

serialVersionUID是实现序列化接口对象的唯一标识,其是为了验证反序列化后的对象是否和反序列化之前的对象类型是否兼容。

其实就是序列版本号,全局常量(对于类来说是唯一的),用来识别该类

总之,我们在提供异常类的时候,都要加上serialVersionUID,用来唯一标识当前类,便于在传输过程中知道是它。

如果没有写这一句话,系统也会自动赋值一个,只不过赋的可能不确定,当修改类的时候,这个值可能会变化,在一些场景下会影响代码的正确性。

我们可以改一下,然后拿过来:

public class BelowZeroException extends Exception{

    static final long serialVersionUID = -33875169948L;

}

接下来,下边都是构造器

模仿这个,我们也提供几个重载的构造器,根据实际问题,想造哪一个就用指定的即可。如下:

package yuyi02;

public class BelowZeroException extends Exception{

    static final long serialVersionUID = -33875169948L;
    
    public BelowZeroException(){

    }

    public BelowZeroException(String name){
        super(name);    //Exception类的super是Throwable,这里super是Exception
    }

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

2、如何使用自定义异常类

上面的例子中,因为继承于异常体系了,所以就不会报错。

package yuyi02;

public class ThrowTest {
    public static void main(String[] args) {
        Student s1=new Student();

        //手动抛出异常类的对象--普通异常 (非运行时异常)
        try {
            s1.regist(-10);
            System.out.println(s1);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

class Student{
    int id;

    public void regist(int id) throws Exception{
        if(id>0){
            this.id=id;
        }else{

            //自定义异常
            throw new BelowZeroException("输入的id非法");
        }
    }

    @Override
    public String toString() {
        return "Student{" +
        "id=" + id +
        '}';
    }
}

此时BelowZeroException以多态的方式抛给了Exception类型,然后上面接收之后打印堆栈信息。

运行之后,可以看到是我们自定义的类型:

image.png

输入的id非法”其实就是Message里面的内容:

image.png

一般不用getMessage,因为printStackTrace里面包含了getMessage里面的信息。

自定义异常类不会自动抛,必须要我们自己throw。

3、代码及总结

3.1 代码

🌱代码

【BelowZeroException.java】

package yuyi02;

/**
 * ClassName: BelowZeroException
 * Package: yuyi02
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/18 0018 9:49
 */
public class BelowZeroException extends Exception{

    static final long serialVersionUID = -33875169948L;
    public BelowZeroException(){

    }

    public BelowZeroException(String name){
        super(name);    //Exception类的super是Throwable,这里super是Exception
    }

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

【ThrowTest.java】

package yuyi02;

/**
 * ClassName: ThrowTest
 * Package: yuyi02
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/16 0016 11:39
 */
public class ThrowTest {
    public static void main(String[] args) {
        Student s1=new Student();

        //手动抛出异常类的对象--普通异常 (非运行时异常)
        try {
            s1.regist(-10);
            System.out.println(s1);
        }catch(Exception e){
            e.printStackTrace();
            //System.out.println(e.getMessage());
        }
    }
}

class Student{
    int id;

    public void regist(int id) throws Exception{
        if(id>0){
            this.id=id;
        }else{

            //自定义异常
            throw new BelowZeroException("输入的id非法");
        }
    }

    @Override
    public String toString() {
        return "Student{" +
        "id=" + id +
        '}';
    }
}

🍺输出

image.png

3.2 总结

<1> 如何自定义异常类?

① 继承于现有的异常体系。通常继承于RuntimeException \ Exception

② 通常提供几个重载的构造器

③ 提供一个全局常量,声明为:static final long serialVersionUID

<2> 如何使用自定义异常类?

  • 在具体的代码中,满足指定条件的情况下,需要手动的使用"throw + 自定义异常类的对象"方式,将异常对象抛出。
  • 如果自定义异常类是非运行时异常,则必须考虑如何处理此异常类的对象。(具体的:①try-catch-finallythrows

(3)注意点

  1. 自定义的异常只能通过throw抛出。
  2. 自定义异常最重要的是异常类的名字message属性。当异常出现时,可以根据名字判断异常类型。比如:TeamException("成员已满,无法添加");TeamException("该员工已是某团队成员");
  3. 自定义异常对象只能手动抛出。抛出后由try..catch处理,也可以甩锅throws给调用者处理。

二、练习

(1)练习1

🌋题目描述

案例:游戏角色

在一款角色扮演游戏中,每一个人都会有名字和生命值,角色的生命值不能为负数。

要求:当一个人物的生命值为负数的时候需要抛出自定义的异常。

操作步骤描述:

(1)自定义异常类NoLifeValueException继承RuntimeException

①提供空参和有参构造

②在有参构造中,需要调用父类的有参构造,把异常信息传入

(2)定义Person类

①属性:名称(name)和生命值(lifeValue)

②提供setter和getter方法:

在setLifeValue(int lifeValue)方法中,首先判断,如果 lifeValue为负数,就抛出NoLifeValueException,

异常信息为:生命值不能为负数:xx;

然后再给成员lifeValue赋值。

③提供空参构造

④提供有参构造:使用setXxx方法给name和lifeValue赋值

(3)定义测试类Exer3

① 使用满参构造方法创建Person对象,生命值传入一个负数

由于一旦遇到异常,后面的代码的将不在执行,所以需要注释掉上面的代码

② 使用空参构造创建Person对象

调用setLifeValue(int lifeValue)方法,传入一个正数,运行程序

调用setLifeValue(int lifeValue)方法,传入一个负数,运行程序

③ 分别对①和②处理异常和不处理异常进行运行看效果


🌱代码

【NoLifeValueException.java】

package yuyi02.exer.exer3;

/**
 * ClassName: NoLifeValueException
 * Package: yuyi02.exer.exer3
 * Description:
 *  (1)自定义异常类NoLifeValueException继承RuntimeException
 * ①提供空参和有参构造
 * ②在有参构造中,需要调用父类的有参构造,把异常信息传入
 * @Author 雨翼轻尘
 * @Create 2024/1/18 0018 11:47
 */
public class NoLifeValueException extends RuntimeException{ //运行时异常,就不用throws处理了

    static final long serialVersionUID = -703489715766939L;

    public NoLifeValueException() {

    }

    public NoLifeValueException(String message) {
        super(message);
    }

}

【Person.java】

package yuyi02.exer.exer3;

/**
 * ClassName: Person
 * Package: yuyi02.exer.exer3
 * Description:
 * (2)定义Person类
 *  ①属性:名称(name)和生命值(lifeValue)
 *  ②提供setter和getter方法:
 *      在setLifeValue(int lifeValue)方法中,首先判断,如果 lifeValue为负数,就抛出NoLifeValueException,
 *      异常信息为:生命值不能为负数:xx;
 *      然后再给成员lifeValue赋值。
 *  ③提供空参构造
 *  ④提供有参构造:使用setXxx方法给name和lifeValue赋值
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/18 0018 11:52
 */
public class Person {
    private String name;   //姓名
    private int lifeValue;  //生命值

    public void setName(String name){
        this.name=name;
    }

    public String getName(){
        return name;
    }

    public int getLifeValue() {
        return lifeValue;
    }

    public void setLifeValue(int lifeValue) {
        if (lifeValue<0){
            throw new NoLifeValueException("生命值不能为负数:"+lifeValue);
        }
        this.lifeValue = lifeValue;
    }

    public Person() {

    }

    public Person(String name, int lifeValue) {
        this.name = name;   //还可以使用setName(name);

        //若传入的lifevalue是负数,该报异常还是报
        setLifeValue(lifeValue);    //注意这里不是 this.lifeValue=lifeValue;
    }

    @Override
    public String toString() {
        return "Person{" +
        "name='" + name + '\'' +
        ", lifeValue=" + lifeValue +
        '}';
    }
}

【Exer3.java】

package yuyi02.exer.exer3;

/**
 * ClassName: Exer3
 * Package: yuyi02.exer.exer3
 * Description:
 *(3)定义测试类Exer3
 *  ① 使用满参构造方法创建Person对象,生命值传入一个负数
 *      由于一旦遇到异常,后面的代码的将不在执行,所以需要注释掉上面的代码
 *  ② 使用空参构造创建Person对象
 *      调用setLifeValue(int lifeValue)方法,传入一个正数,运行程序
 *      调用setLifeValue(int lifeValue)方法,传入一个负数,运行程序
 *  ③ 分别对①和②处理异常和不处理异常进行运行看效果
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/18 0018 12:06
 */
public class Exer3 {
    public static void main(String[] args) {
        //1.使用有参的构造器
        Person p1=new Person("Tom",10);
        System.out.println(p1);

        //        Person p2=new Person("Jack",-20);   //此时在构造器里面赋了一个负数,在setLifeValue里面就会抛出异常,因为是运行时异常,不用处理
        //        System.out.println(p2); //在调用构造器的时候,异常已经出来了,没有捕获,程序结束,这一句不会执行

        try{
            Person p2=new Person("Jack",-20);
            System.out.println(p2);
        }catch(NoLifeValueException e){
            System.out.println(e.getMessage());
        }

        //2.使用空参的构造器
        Person p3=new Person();
        p3.setName("Jerry");
        p3.setLifeValue(-30);

    }
}

🍺输出结果

image.png

🗳?注意

在调用构造器的时候,异常已经出来了,没有捕获,程序结束,输出语句不会执行。

public class Exer3 {
    public static void main(String[] args) {
    	//1.
        Person p2=new Person("Jack",-20);   //此时在构造器里面赋了一个负数,在setLifeValue里面就会抛出异常,因为是运行时异常,不用处理
        System.out.println(p2); //在调用构造器的时候,异常已经出来了,没有捕获,程序结束,这一句不会执行
    }
}

此时没有处理,若是想处理,不过是加上try-catch而已,如下:

public class Exer3 {
    public static void main(String[] args) {
    	try{
            Person p2=new Person("Jack",-20);   
        	System.out.println(p2); 
        }catch(NoLifeValueException e){
            System.out.println(e.getMessage());
        }
    }
}

输出:

image.png

其实没有必要处理它,因为是运行时异常。

(2)练习2

🌋题目描述

编写应用程序DivisionDemo.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。

对数据类型不一致(NumberFormatException)、缺少命令行参数(ArrayIndexOutOfBoundsException)、
除0(ArithmeticException)及输入负数(BelowZeroException 自定义的异常)进行异常处理。

提示:

(1)在主类(DivisionDemo)中定义异常方法(divide)完成两数相除功能。

(2)在main()方法中调用divide方法,使用异常处理语句进行异常处理。

(3)在程序中,自定义对应输入负数的异常类(BelowZeroException)。

(4)运行时接受参数 java DivisionDemo 20 10 //args[0]=“20” args[1]=“10”

(5)Interger类的static方法parseInt(String s)将s转换成对应的int值。
如:int a=Interger.parseInt("314"); //a=314;


🗳?分析

  • 数据类型不一致(NumberFormatException):题目要求输入两个数,不能是abc之类的。
  • 缺少命令行参数(ArrayIndexOutOfBoundsException):main方法形参可以接收,使用的是java DivisionDemo 20 10的方式,需要接收两个数,若此时只输入了一个数,就报这个异常。
  • 除0(ArithmeticException):两个数相除,若分母是0,就会报这个异常。
  • 输入负数(BelowZeroException 自定义的异常):输入负数就报这个异常。

<1> 得到两个数

通过参数传进来两个数,不过这里是String类型的,所以下面需要转换为int类型。

image.png

然后用int类型的变量接收,如下:

public class DivisionDemo {
    public static void main(String[] args) {    //通过参数来传进来两个数
        //1.获取两个数
        int m=Integer.parseInt(args[0]);
        int n=Integer.parseInt(args[1]);
    }
}

不会转换的去看“包装类”这一节的内容,链接:https://blog.csdn.net/m0_55746113/article/details/134990781?spm=1001.2014.3001.5502

<2> 除法

除法的需求是放在divide方法里面的,这里就声明为静态的方法了(非静态的话就需要拿当前对象来调用了),返回值是int类型的。如下:

//2.除法操作
public static int divide(int m, int n){
    return m/n;
}

现在来考虑条件,首先两个数不能为负数。(题目要求)

若有一个为负数,就抛出一个异常对象,比如自定义异常BelowZeroException,如下:

//2.除法操作
public static int divide(int m, int n){
    if(m<0 || n<0){
        //手动抛出异常类对象
        throw new BelowZeroException();
    }
    return m/n;
}

假设BelowZeroException继承于一个非运行时异常Exception。

①继承于现有的异常体系

public class BelowZeroException extends Exception{  //假设继承于一个非运行时异常

}

②提供构造器

一般提供空参和带有message的:

public class BelowZeroException extends Exception{  //假设继承于一个非运行时异常

    public BelowZeroException() {

    }

    public BelowZeroException(String message) {
        super(message);
    }
}

③序列号

随便写一个号:

public class BelowZeroException extends Exception{  //假设继承于一个非运行时异常
    static final long serialVersionUID = -33875169934229948L;
    
    public BelowZeroException() {

    }

    public BelowZeroException(String message) {
        super(message);
    }
}

此时divide方法里面就可以使用BelowZeroException异常了,比如:

image.png

此时是“非运行时异常”,既然抛了,就要考虑处理它。

比如这里使用throws解决:

//2.除法操作
public static int divide(int m, int n) throws BelowZeroException{
    if(m<0 || n<0){
        //手动抛出异常类对象
        throw new BelowZeroException("输入负数了");
    }
    return m/n;
}

这里还要考虑除法分母为0的异常,系统其实已经自动考虑throw了,所以不用管它。

<3> 解决异常

现在就可以在main方法里面调用divide方法了,因为是静态方法,就不需要使用对象来调用了。

如下:

image.png

因为divide方法将异常抛出去了,所以此时在main方法里面需要解决异常。

题目要求异常都处理一下,所以这里也将运行时异常一并处理了,实际上运行时异常可以不做处理的。
这里就演示一下吧。

将这些代码都框起来,快捷键Ctrl+Alt+T,选择第6个try-catch

image.png

可以看到,自动生成了:

image.png

throw new RuntimeException(e); 系统自动生成把非运行时异常throw出去了,这里咱们不用它,直接打印堆栈信息:e.printStackTrace();

由于各个异常是并列关系,所以谁前谁后都一样:

public static void main(String[] args) {    //通过参数来传进来两个数
    try {
        //1.获取两个数
        int m=Integer.parseInt(args[0]);
        int n=Integer.parseInt(args[1]);

        int result = divide(m,n);
        System.out.println("结果为:"+result);
    } catch (BelowZeroException e) {
        //e.printStackTrace();
        System.out.println(e.getMessage());
    } catch (NumberFormatException e){
        System.out.println("数据类型不一致");
    } catch (ArrayIndexOutOfBoundsException e){
        System.out.println("缺少命令行参数");  //角标越界
    } catch (ArithmeticException e){
        System.out.println("除0了");
    }
}

🌱代码

【BelowZeroException.java】

package yuyi02.exer.exer4;

/**
 * ClassName: BelowZeroException
 * Package: yuyi02.exer.exer4
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/18 0018 15:40
 */
public class BelowZeroException extends Exception{  //假设继承于一个非运行时异常
    static final long serialVersionUID = -33875169934229948L;
    public BelowZeroException() {

    }

    public BelowZeroException(String message) {
        super(message);
    }
}

【DivisionDemo.java】

package yuyi02.exer.exer4;

/**
 * ClassName: DivisionDemo
 * Package: yuyi02.exer.exer4
 * Description:
 *   编写应用程序DivisionDemo.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。
 *         对数据类型不一致(NumberFormatException)、缺少命令行参数(ArrayIndexOutOfBoundsException)、
 *     除0(ArithmeticException)及输入负数(BelowZeroException 自定义的异常)进行异常处理。
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/18 0018 15:08
 */
public class DivisionDemo {
    public static void main(String[] args) {    //通过参数来传进来两个数
        try {
            //1.获取两个数
            int m=Integer.parseInt(args[0]);
            int n=Integer.parseInt(args[1]);

            int result = divide(m,n);
            System.out.println("结果为:"+result);
        } catch (BelowZeroException e) {
            //e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (NumberFormatException e){
            System.out.println("数据类型不一致");
        } catch (ArrayIndexOutOfBoundsException e){
            System.out.println("缺少命令行参数");  //角标越界
        } catch (ArithmeticException e){
            System.out.println("除0了");
        }
    }

    //2.除法操作
    public static int divide(int m, int n) throws BelowZeroException{
        if(m<0 || n<0){
            //手动抛出异常类对象
            throw new BelowZeroException("输入负数了");
        }
        return m/n;
    }
}

🍺输出结果

image.png

这是因为此时没有传值,没有传值的话,此时数组长度为0,调用args[0]和args[1]就会越界访问,此时的异常是ArrayIndexOutOfBoundsException缺少命令行参数)。


?不同情况

<1> 正常情况

现在来做个配置,在Idea中,“运行”->“编辑配置”:

image.png

针对当前的Modul,在这里写参数:

image.png

比如12和2(12给了args[0],2给了args[1]),如下:

image.png

“确定”之后再次运行程序,它会正常做除法,结果是6,如下:

image.png

<2> ArrayIndexOutOfBoundsException缺少命令行参数

现在少写一个数,如下:

image.png

<3> NumberFormatException数据类型不一致

字符串“abc”无法转化为int类型,如下:

image.png

<4> ArithmeticException除0了

image.png

<5> BelowZeroException输入负数了

image.png

此题有综合性,用到了throws和try-catch,还有常见的异常类型,并且还手动抛异常了,这个异常还是自定义的。

在实际开发中,运行时异常其实不用做处理,只针对于非运行时异常做处理即可。

(3)笔试题

下面代码输出结果是多少?

🌱代码

package yuyi02.exer.exer2;

/**
 * ClassName: ReturnExceptionDemo
 * Package: yuyi02.exer.exer2
 * Description:笔试题
 *   
 * @Author 雨翼轻尘
 * @Create 2024/1/18 0018 11:11
 */
public class ReturnExceptionDemo {
    static void methodA() throws Exception{
        try {
            System.out.println("进入方法A");
            throw new Exception("制造异常");
        } finally {
            System.out.println("用A方法的finally");
        }
    }

    static void methodB() {
        try {
            System.out.println("进入方法B");
            return;
        } finally {
            System.out.println("调用B方法的finally");
        }
    }

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

        methodB();
    }
}

🍺输出结果

image.png

🗳?分析

image.png

三、小结与小悟

(1)小结:异常处理5个关键字

image.png

类比:上游排污,下游治污

(2)感悟

小哲理

世界上最遥远的距离,是我在if里你在else里,似乎一直相伴又永远分离;

世界上最痴心的等待,是我当case你是switch,或许永远都选不上自己;

世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。到那时,再来期待我们的finally

歌词

image.png

文章来源:https://blog.csdn.net/m0_55746113/article/details/135678410
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。