Java异常处理--throw手动抛出异常对象

发布时间:2024年01月18日

一、手动抛出异常对象:throw

(1)两种方式

之前说的几个步骤:

①此时执行几行代码,在执行的过程中,可能出现一些问题,比如将字符串“abc”转换成Integer类型的,肯定是没有办法转化。这时候它会在相应代码位置自动生成一个NumberFormatException的对象,这个对象就被抛出去了。

②针对抛出的异常对象,考虑catch处理(捕获);若是搞不定,还可以throws。(异常处理的两种方式

现在针对“生成异常对象”的环节,除了系统自动抛之外,可能还会出现手动抛出异常对象的场景。

手动抛出异常对象,也是要处理的。只要是抛出了异常对象,不去处理的话,程序就会停止。

?Java 中异常对象的生成有两种方式:

  • 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,那么针对当前代码,就会在后台自动创建一个对应异常类的实例对象并抛出。
  • 由开发人员手动创建new 异常类型([实参列表]);,如果创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样,但是一旦throw抛出,就会对程序运行产生影响了。

🎲为什么要主动抛出异常呢?

在程序执行的时候,有时候会出现不满足要求的场景。比如将字符串“abc”转换为Integer类型的,这就是转换不了的,对于这种场景,程序给我们预知道这些问题,帮我们把这些问题给操作了,意思就是,若是出现这种不合理的现象,它就会自动抛出一个异常对象。
但是还是会有一些实际问题,系统没办法给我们抛,就需要自己来。

比如现在有一个Student,他有一个id属性,要给id赋值,这个id最起码要是一个正数,一旦给这个id赋值了负数,就不太合理。以前我们是写一个输出语句,现在可以拋一个异常。在Java语言层面,没有说id不能是负数,这是我们实际情况需要的一个要求。

所以,如果在实际开发当中,有一些具体的要求,这些要求在实际操作中不满足的时候,我们就可以主动去抛出一个异常对象。

在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的异常对象。

(2)使用格式

🎲如何实现手动抛出异常?

在方法内部,满足指定条件的情况下,使用"throw 异常类的对象"的方式抛出。

注意这里是throw,后面是异常的对象,既然是对象,就需要new一个,后面再跟上想抛出类型的异常。

如下:

throw new 异常类名(参数);

注意点:throw后的代码不能被执行,编译不通过。

throw语句抛出的异常对象,和JVM自动创建和抛出的异常对象一样。

  • 如果是编译时异常类型的对象,同样需要使用throws或者try…catch处理,否则编译不通过。
  • 如果是运行时异常类型的对象,编译器不提示。
  • 可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
throw new String("want to throw");

(3)举例

1、手动抛运行时异常

现在有一个Student,他有一个id属性,要给id赋值,这个id最起码要是一个正数,一旦给这个id赋值了负数,就不太合理。

以前我们是写一个输出语句,现在可以拋一个异常。在Java语言层面,没有说id不能是负数,这是我们实际情况需要的一个要求。

以往我们只能这样表示id输入非法(输入的是负数),如下:

public class ThrowTest {

}

class Student{
    int id;

    public void regist(int id){
        if(id>0){
            this.id=id;
        }else{
            System.out.println("输入的id非法");
        }
    }
}

现在我们可以手动抛出异常类的对象,一旦输入的id不行,就不让程序继续往下执行,如下:

class Student{
    int id;

    public void regist(int id){
        if(id>0){
            this.id=id;
        }else{
            //System.out.println("输入的id非法");

            //手动抛出异常类的对象
            throw new RuntimeException("输入的id非法");
        }
    }
}

这里抛的是一个运行时异常,抛出了一个异常对象,下一步就需要考虑处理了,运行时异常可以考虑不处理

所以现在不需要try-catch或者throws。

调用一下:

🌱情况1

package yuyi02;

public class ThrowTest {
    public static void main(String[] args) {
        Student s1=new Student();
        s1.regist(10);
        System.out.println(s1);
    }
}

class Student{
    int id;

    public void regist(int id){
        if(id>0){
            this.id=id;
        }else{
            //System.out.println("输入的id非法");

            //手动抛出异常类的对象
            throw new RuntimeException("输入的id非法");
        }
    }

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

输出结果:

image.png

这是一个正常情况。


🌱情况2

package yuyi02;

public class ThrowTest {
    public static void main(String[] args) {
        Student s1=new Student();
        s1.regist(-10);
        System.out.println(s1);
    }
}

class Student{
    int id;

    public void regist(int id){
        if(id>0){
            this.id=id;
        }else{
            //System.out.println("输入的id非法");

            //手动抛出异常类的对象
            throw new RuntimeException("输入的id非法");
        }
    }

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

此时传入regist中的是-10,会进入else逻辑中,会抛出异常对象。

这个异常对象抛出来之后也没有进行处理,也没有try-catch-finally,也没有throws。(运行时异常不做处理)

所以,这个时候出现的异常对象,导致程序终止,main方法后面的输出语句就无法执行了。

看一下输出结果:

image.png

可以看到,它里面是一个RuntimeException,因为我们造的就是一个RuntimeException的对象,里面的信息就是"输入的id非法"。我们在这里写的信息其实就是Message里面的内容。

?补充

若是现在处理这个“运行时异常”,可以这样try-catch一下:

package yuyi02;

public class ThrowTest {
    public static void main(String[] args) {
        Student s1=new Student();
        try {
            s1.regist(-10);
            System.out.println(s1);
        }catch (RuntimeException e){
            e.printStackTrace();
        }

    }
}

class Student{
    int id;

    public void regist(int id){
        if(id>0){
            this.id=id;
        }else{
            //手动抛出异常类的对象
            throw new RuntimeException("输入的id非法");
        }
    }

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

输出结果和没有处理的时候一样,所以说一般不做处理,如下:

image.png

这里的“输入的id非法”其实就是getMessage,它里面就是构造器传进来的数据。

public class ThrowTest {
    public static void main(String[] args) {
        Student s1=new Student();
        try {
            s1.regist(-10);
            System.out.println(s1);
        }catch (RuntimeException e){
            System.out.println(e.getMessage());
        }
    }
}

class Student{
    int id;

    public void regist(int id){
        if(id>0){
            this.id=id;
        }else{
            //System.out.println("输入的id非法");

            //手动抛出异常类的对象
            throw new RuntimeException("输入的id非法");
        }
    }

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

现在打印的就是这个内容,如下:

image.png

2、手动抛普通的异常

比如这样:

package yuyi02;

class Student{
    int id;

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

这个时候就要小心一点了,因为它不是运行时异常了,这种运行方式就需要处理了。如下:

image.png

出了异常就要处理了,可以try-catch或者throws

假设现在不在regist方法里面try-catch,那我们就throws一下。

因为下面thow的是一个Exception,那么上面throws的也要是Exception。如下:

image.png

现在不是运行时异常,就需要处理了。

现在是一个普通的Exception,而且将这个问题抛到main方法那边了。main方法就需要处理这个异常了,这时候必须要try-catch(catch里面的异常就是针对throw过来的那个,现在throw过来了Exception,所以catch里面要写Exception),如下:

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

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

🌱整体代码

package yuyi02;

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

        //2、手动抛出异常类的对象--普通异常 (非运行时异常)
        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{

            //2、手动抛出异常类的对象--普通异常 (非运行时异常)
            throw new Exception("输入的id非法");
        }
    }

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

注意点:throw后的代码不能被执行,编译不通过。比如:

image.png

🍺输出如下

image.png

3、代码

上面两种的代码,整体如下:

🌱代码

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();

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


        //2、手动抛出异常类的对象--普通异常 (非运行时异常)
        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{
            //System.out.println("输入的id非法");

            //1、手动抛出异常类的对象--运行时异常
            //throw new RuntimeException("输入的id非法");

            //2、手动抛出异常类的对象--普通异常 (非运行时异常)
            throw new Exception("输入的id非法");
        }
    }

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

(4)使用注意点

无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有被try…catch合理的处理,都会导致程序崩溃。

throw语句会导致程序执行流程被改变,throw语句是明确抛出一个异常对象,因此它下面的代码将不会执行

如果当前方法没有try…catch处理这个异常对象,throw语句就会代替return语句提前终止当前方法的执行,并返回一个异常对象给调用者。

package com.atguigu.keyword;

public class TestThrow {
    public static void main(String[] args) {
        try {
            System.out.println(max(4,2,31,1));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            System.out.println(max(4));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            System.out.println(max());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static int max(int... nums){
        if(nums == null || nums.length==0){
            throw new IllegalArgumentException("没有传入任何整数,无法获取最大值");
        }
        int max = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }
}

(5)自动VS手动

🎲如何理解"自动 vs 手动"抛出异常对象?

①过程1:“”(产生异常对象)

  • “自动抛” : 程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,自动生成对应异常类的对象,并将此对象抛出。一旦抛出,此程序就不执行其后的代码了。
  • “手动抛” :程序在执行的过程当中,不满足指定条件的情况下,我们主动的使用"throw + 异常类的对象"方式抛出异常对象。

②过程2:“” (处理异常对象)

  • 狭义上讲:try-catch的方式捕获异常,并处理。
  • 广义上讲:把“抓”理解为“处理”。则此时对应着异常处理的两种方式:① try-catch-finallythrows

?注意

1、其实,所谓的自动抛,不过不是我们自己写的而已。在源码中,它其实也是用“throw+异常对象”来做的。这里只是理解为“自动抛”。

2、throwsthrow是合作关系,throws是产生异常对象的过程,throw是处理异常对象的过程。

二、练习

(1)练习1

🌋题目描述

修改chapter08_oop3中接口部分的exer2,在ComparableCircle接口compareTo()中抛出异常。

接口这一篇的练习2中有详细说明,链接:https://blog.csdn.net/m0_55746113/article/details/134687578?spm=1001.2014.3001.5502

image.png

【Circle.java】

package yuyi02.exer.exer1;

/**
 * ClassName: Circle
 * Package: yuyi03
 * Description:
 *      定义一个Circle类,声明radius属性,提供getter和setter方法
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 14:52
 */
public class Circle {   //两个Circle对象不能够比较大小
    public double radius;   //半径

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public Circle() {

    }

    public Circle(double radius) {
        this.radius = radius;
    }

    //toString方法
    @Override
    public String toString() {
        return "Circle{" +
        "radius=" + radius +
        '}';
    }
}

【ComparableCircle.java】

package yuyi02.exer.exer1;

/**
 * ClassName: ComparableCircle
 * Package: yuyi03
 * Description:
 *      定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
 *      在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 14:56
 */
public class ComparableCircle extends Circle implements CompareObject {
    //根据对象半径的大小,比较对象的大小(和之前说的equals很像)
    @Override
    public int compareTo(Object o) {
        if(this==o){    //判断当前对象与o是不是指向同一个
            return 0;   //若地址一样,则半径肯定一致,直接返回0
        }
        if(o instanceof ComparableCircle){  //判断是否是当前类的对象
            ComparableCircle c=(ComparableCircle) o;    //若是当前类对象,先强转一下 (从父类对象强转成子类才能调用子类特有的结构)
            return Double.compare(this.getRadius(),c.getRadius()); //API里面有一个类就叫Double,里面有一个方法叫compare(),里面传入两个double类型的值,就会自动比较它们的大小,返回的就是一个int类型的值,直接return即可

        }else{  //当这个对象不是当前实例
            //return 2;  如果输入类型不匹配,则返回2
            throw new RuntimeException("输入类型不匹配");
        }

    }

    public ComparableCircle() {

    }

    public ComparableCircle(double radius) {
        super(radius);
    }
}

【CompareObject.java】

package yuyi02.exer.exer1;

/**
 * ClassName: CompareObject
 * Package: yuyi03
 * Description:
 *      定义一个接口用来实现两个对象的比较。
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 10:09
 */
public interface CompareObject {    //自定义一个接口来比较对象大小
    //若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
    public int compareTo(Object o); //这个是抽象方法(省略了abstract),虽然它没有方法体,但是这个方法是做什么的,形参是什么意思,返回值类型是什么都完全确定了,只是细节没有确定
}

【InterfaceTest.java】

package yuyi02.exer.exer1;

/**
 * ClassName: InterfaceTest
 * Package: yuyi03
 * Description:
 *      定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 15:43
 */
public class InterfaceTest {
    public static void main(String[] args) {
        ComparableCircle c1=new ComparableCircle(2.3);
        ComparableCircle c2=new ComparableCircle(5.3);

        int compareValue=c1.compareTo(c2);
        if(compareValue>0){
            System.out.println("c1对象大");
        } else if (compareValue<0) {
            System.out.println("c2对象大");
        }else {
            System.out.println("c1与c2一样大");
        }
    }
}

🍰分析

接口CompareObject里面有一个方法compareTo,用来比较对象大小。

Circle本身不能比较大小,它的子类ComparableCircle继承于Circle的同时又实现了接口CompareObject,它就具备了比较大小的规范。

在ComparableCircle中将方法compareTo重写了。

若比较的两者一致就是0。

不一致的话,若是ComparableCircle类型的,先强转然后比较;若不是ComparableCircle类型的,当时没有学到异常,只能用return 2将就一下,现在来看的话,不是ComparableCircle类型的就直接抛一个异常即可。

如下:

image.png

这时候,抛一个异常最为合适,因为返回正数、负数、0都不太合适。

现在抛的是运行时异常RuntimeException,抛了一个对象而已,此时并没有处理。

一般抛出异常之后,都考虑要进行处理,只不过此时抛的是运行时异常,就不用进行处理了。

若此时用的是一般异常,就需要进行处理了。比如现在throw了一个Exception,如下:

image.png

现在就要处理了,要不然程序过不去。

怎么处理?要么throws,要么try-catch

比如现在往上抛,针对当前抛的类型,也应该是Exception,如下:

image.png

这时候怎么还报错呢?

看一下错误信息:

image.png

这是因为接口里面没有抛异常,如下:

image.png

实现方法(重写方法)里面不能抛比接口里面更大的,所以此时接口里面也需要做处理:

image.png

以后也会碰见这样的场景,在接口里面,明明是一个抽象方法,它也会抛出一个异常,这就是为了配合在具体实现的时候可能会抛出的异常。


现在还有一个地方报错,如下:

image.png

这是因为刚才处理异常的时候,我们采用了第二种方式,就是throws往上抛。

所以在main方法里面,就必须要对异常进行处理了。

可以使用try-catch进行处理,如下:

image.png

🌱代码

【Circle.java】

package yuyi02.exer.exer1;

/**
 * ClassName: Circle
 * Package: yuyi03
 * Description:
 *      定义一个Circle类,声明radius属性,提供getter和setter方法
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 14:52
 */
public class Circle {   //两个Circle对象不能够比较大小
    public double radius;   //半径

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public Circle() {

    }

    public Circle(double radius) {
        this.radius = radius;
    }

    //toString方法
    @Override
    public String toString() {
        return "Circle{" +
        "radius=" + radius +
        '}';
    }
}

【CompareObject.java】

package yuyi02.exer.exer1;

/**
 * ClassName: CompareObject
 * Package: yuyi03
 * Description:
 *      定义一个接口用来实现两个对象的比较。
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 10:09
 */
public interface CompareObject {    //自定义一个接口来比较对象大小
    //若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
    public int compareTo(Object o) throws Exception; //这个是抽象方法(省略了abstract),虽然它没有方法体,但是这个方法是做什么的,形参是什么意思,返回值类型是什么都完全确定了,只是细节没有确定
}

【ComparableCircle.java】

package yuyi02.exer.exer1;

/**
 * ClassName: ComparableCircle
 * Package: yuyi03
 * Description:
 *      定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
 *      在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 14:56
 */
public class ComparableCircle extends Circle implements CompareObject {
    //根据对象半径的大小,比较对象的大小(和之前说的equals很像)
    @Override
    public int compareTo(Object o) throws Exception{
        if(this==o){    //判断当前对象与o是不是指向同一个
            return 0;   //若地址一样,则半径肯定一致,直接返回0
        }
        if(o instanceof ComparableCircle){  //判断是否是当前类的对象
            ComparableCircle c=(ComparableCircle) o;    //若是当前类对象,先强转一下 (从父类对象强转成子类才能调用子类特有的结构)
            return Double.compare(this.getRadius(),c.getRadius()); //API里面有一个类就叫Double,里面有一个方法叫compare(),里面传入两个double类型的值,就会自动比较它们的大小,返回的就是一个int类型的值,直接return即可

        }else{  //当这个对象不是当前实例
            //throw new RuntimeException("输入类型不匹配");
            throw new Exception("输入的类型不匹配");
        }

    }

    public ComparableCircle() {

    }

    public ComparableCircle(double radius) {
        super(radius);
    }
}

【InterfaceTest.java】

package yuyi02.exer.exer1;

/**
 * ClassName: InterfaceTest
 * Package: yuyi03
 * Description:
 *      定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
 * @Author 雨翼轻尘
 * @Create 2023/11/28 0028 15:43
 */
public class InterfaceTest {
    public static void main(String[] args) {
        ComparableCircle c1=new ComparableCircle(2.3);
        ComparableCircle c2=new ComparableCircle(5.3);

        try {
            int compareValue=c1.compareTo(c2);
            if(compareValue>0){
                System.out.println("c1对象大");
            } else if (compareValue<0) {
                System.out.println("c2对象大");
            }else {
                System.out.println("c1与c2一样大");
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

🍺输出结果

image.png

(2)面试题

🎲面试题:throw 和 throws 的区别?

答:“上游排污,下游治污”,污-异常对象;排-throw;治-throws。

throws从使用上来讲,是使用在方法的声明上,后面跟异常类型,指明将异常向上一层抛出,属于异常处理的方式。

throw是使用在方法内部,后面跟的是异常类的对象,表示手动抛出一个指定异常类的对象。

throw用来产生异常对象(第一个环节),throws是针对产生的异常对象,如何进行处理(第二个环节)。

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