37讲 异常

1 异常简述

1.1 异常简述

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,代码少了一个分号,那么运行出来结果是 java.lang.Error 的错误;

System.out.println(10/0);,是因为用0做了除数,会抛出 java.lang.ArithmeticException 的异常。

异常的作用

  1. 用来查询bug的关键参考信息。当程序出现bug时,可通过异常快速找出问题所在。
  2. 异常可作为方法内部的一种特殊的返回值 ,以告知调用者底层的执行情况。详见下文 《自定义异常》 。

1.2 异常体系

在这里插入图片描述

  • Error :系统级别的严重问题,我们开发人员不需要处理。

  • Exception可处理异常的顶级父类。表示程序本身可以处理的问题。

    • RuntimeException及其子类运行时异常 。在编译期不检查,运行时才会出现异常提醒。
    • 非RuntimeException编译时异常 。编译时就会提醒处理,否则程序不能通过编译。

2 编译时异常和运行时异常

Java异常被分为两大类:

  1. 编译时异常(受检异常)。 编译期 :Java文件通过Javac命令编译成字节码文件
  2. 运行时异常(非受检异常)。 运行期 :字节码文件通过Java命令运行

2.1 运行时异常

运行时异常是指 RuntimeException 类及其子类。

特点: 在编译期不检查,运行时才会出现异常提醒。

请添加图片描述

2.2 编译时异常

除了运行时异常的异常都是编译时异常。

特点: 必须显示处理,否则程序就会发成错误,无法通过编译
在这里插入图片描述

3 Throwable常用方法

以下方法均不会让程序停止运行。

方法说明
public String getMessage()返回此throwable的详细信息字符串(异常原因)
public String toString()返回此可抛出简短描述(异常的类名+异常原因)
public void printStackTrace()以红色字体把异常的错误信息输出在控制台(异常的类名+异常原因+位置)
  1. public String getMessage()//返回此throwable的详细信息字符串(异常原因)
public class ExceptionDemo7 {
    public static void main(String[] args) {
        try {
            System.out.println(1 / 0);
        } catch (ArithmeticException e) {
            String message = e.getMessage();
            System.out.println(message);
        }
        System.out.println("最后一条输出语句");
        
        /*运行结果:
        	 / by zero
			最后一条输出语句
        */
    }
}
  1. public String toString() //返回此可抛出简短描述(异常的类名+异常原因)
public class ExceptionDemo7 {
    public static void main(String[] args) {
        try {
            System.out.println(1 / 0);
        } catch (ArithmeticException e) {
            String s = e.toString();
            System.out.println(s);
        }
        System.out.println("最后一条输出语句");
        
        /*运行结果:
        	 java.lang.ArithmeticException: / by zero
			最后一条输出语句
        */
    }
}
  1. public void printStackTrace() //以红色字体把异常的错误信息输出在控制台(异常的类名+异常原因+位置)
public class ExceptionDemo7 {
    public static void main(String[] args) {
        try {
            System.out.println(1 / 0);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        }
        System.out.println("最后一条输出语句");
        
        /*运行结果:
        	 java.lang.ArithmeticException: / by zero
				at do22Exception.ExceptionDemo7.main(ExceptionDemo7.java:9)
			最后一条输出语句
        */
    }
}

4 异常的处理方式

4.1 JVM的默认处理方案

如果程序出现问题,而我们没有做任何处理,最终JVM会做默认的处理。

默认处理:

  • 在控制台输出:异常的类名、异常原因、异常出现位置。
  • 程序在遇到异常后就停止运行 ,异常下的代码就不会运行。

在这里插入图片描述

4.2 捕获异常

格式:

try {
    可能出现异常的代码;
} catch (异常类名 变量名) {
    遇到异常后的处理代码;
}

目的: 处理运行时异常、编译时异常时,抛出异常且程序不会终止

案例:

public class ExceptionDemo4 {
    public static void main(String[] args) {
        System.out.println("第一条输出语句");
        try {
            //可能出现异常的代码
            System.out.println(1 / 0);
        } catch (ArithmeticException e) {
            System.out.println("0不能做除数, " + e);
        }
        System.out.println("第二条输出语句");
        System.out.println("第三条输出语句");
    }
}

运行结果:

第一条输出语句
0不能做除数, java.lang.ArithmeticException: / by zero
第二条输出语句
第三条输出语句

解释:try 中的代码出现异常后,程序会创建一个该异常的对象,再把该异常对象与 catch 中的变量对比。

如果 catch 中的变量可以接收该异常(即两个异常一样),就表示该异常被捕获,程序便会执行 catch 中的代码,且 try...catch 下的代码也会运行。

如果两个异常不一样,还是会执行JVM的默认处理方案。
在这里插入图片描述

可见,遇到可能会发生的异常并成功捕获后,程序会抛出我们给定的处理方案,并且异常不会使程序停止运行。

捕获异常其他细节
  1. 只有 try 中出现了异常,才会执行异常对应 catch 中的代码。
  2. try 中可能会遇到多个异常,如果遇到的第一个异常在 catch 中没有捕获,还是会执行JVM的默认处理方式。如下,遇到的第一个异常是索引越界异常,但是 catch 中只捕获了算数异常。

在这里插入图片描述

  • 所以我们要写所有对应的异常捕获 catch
public class ExceptionDemo5 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println(arr[3]);
            System.out.println(1 / 0);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("索引越界");
            System.out.println("第一条输出语句");
        } catch (ArithmeticException e) {
            System.out.println("0不能做除数");
            System.out.println("第二条输出语句");
        }
        System.out.println("第三条输出语句");
    }
}

运行结果:

索引越界
第一条输出语句
第三条输出语句
  • 且异常中如果存在父子关系,父类一定要写在最下面。

在这里插入图片描述

  1. 如果 try 中遇到了异常,那么从 try 遇到异常的语句往下的语句就不会执行。
public class ExceptionDemo6 {
    public static void main(String[] args) {
        try {
            System.out.println("第一条输出语句");
            System.out.println(1 / 0);
            System.out.println("第二条输出语句");
        } catch (ArithmeticException e) {
            System.out.println("0不能做除数");
        }
        System.out.println("第三条输出语句");
    }
}

运行结果:

第一条输出语句
0不能做除数
第三条输出语句

4.3 抛出异常

当在方法中出现了异常,该方法已经没有执行下去的意义了。此时可以采取抛出异常并终止方法。

4.3.1 throws

有时会出现没有权限使用 try...catch... 来进行异常处理,就需要用到 throws

throws 写在方法定义处,表示声明一个异常。告知调用者,使用本方法可能会出现哪些异常。

格式: throws 异常类名1, 异常类名2, 异常类名3 ...

public void 方法() throws 异常类名1, 异常类名2, 异常类名3 ... {
    ...
}

注意:

  • 这个格式是跟在方法的括号的后面的
  • 编译时异常:必须要写
  • 运行时异常:可以省略不写

处理编译时异常时:

  • 仅抛出异常,程序会异常终止。
  • 谁调用该方法,谁就用 try...catch... 捕获异常并处理。
4.3.2 throw

throw写在方法体内,手动抛出异常对象交给调用者。且throw下面的代码不会再执行。

格式: throw 异常

public void 方法() {
    throw new ArrayIndexOutOfBoundsException();
    //只要抛出了异常,往下的代码就不会在执行
}

处理编译时异常时:

  • 仅抛出异常,程序会异常终止。
  • 谁调用该方法,谁就用 try...catch... 捕获异常并处理。
4.3.3 throws 和 throw 的区别
throwsthrow
用在方法声明后,跟的是异常类名用在方法体内,跟的是异常对象名
表示抛出异常,由该方法的调用者来处理表示抛出异常,由方法体内的语句处理
表示出现异常的一种可能性,并不一定发生执行 throw 一定抛出了某种异常

4.4 综合应用

案例: 键盘录入姓名和年龄。姓名长度在3-10位;年龄范围18-25岁。超出这个范围是异常数据,需要重新录入,直到录入正确。

import java.util.Scanner;

public class ExceptionTest {
    public static void main(String[] args) {
        Student student = new Student();
        Scanner sc = new Scanner(System.in);

        while (true) {
            try {
                System.out.println("请输入姓名:");
                String name = sc.nextLine();
                student.setName(name);
                System.out.println("请输入年龄:");
                String ageStr = sc.nextLine();
                int age = Integer.parseInt(ageStr);
                student.setAge(age);
                //只要执行到这一步,说明没有任何异常,跳出循环。
                break;
            } catch (NumberFormatException e) {
                System.out.println("年龄格式有误,请输入数字");
            } catch (RuntimeException e) {
                System.out.println("姓名长度有误或年龄范围有误");
            }
        }

        System.out.println(student);
    }
}

class Student {
    private String name;
    private int age;

	//省略空参、带参构造,get方法。

    public void setName(String name) {
        //姓名长度在3-10位
        if (name.length() < 3 || name.length() > 10) {
            throw new RuntimeException();
        }
        this.name = name;
    }

    public void setAge(int age) {
        //年龄范围18-25岁
        if (age < 18 || age > 25) {
            throw new RuntimeException();
        }
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

6 自定义异常

6.1 自定义异常简述

在4.4 案例中,虽然抛出、捕获并处理了异常,但是Java并没有提供一个能完美描述”姓名长度有误”,“年龄范围有误“的异常。我们只能通过 RuntimeException 统一处理,显然这是不够直观的,此时我们可以自定义一个异常来描述。

自定义异常的目的: 让控制台的报错信息更加直观,见名知意。

6.2 自定义异常书写步骤

  1. 定义异常类。名字要见名知意。
  2. 提供继承关系。运行时异常继承RuntimeException,编译时异常直接继承Exception
  3. 空参、带参构造。
  4. 在出现异常的地方用 throw new 自定义异常对象抛出

格式:

public class 异常类名 extends 异常 {
    无参构造方法;
    带参构造方法;
}

案例: 使用自定义异常改写”键盘录入姓名和年龄。姓名长度在3-10位;年龄范围18-25岁。超出这个范围是异常数据,需要重新录入,直到录入正确。“

自定义异常类

//自定义异常:姓名格式化异常
public class NameFormatException extends RuntimeException {
    public NameFormatException() {
    }

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

//自定义异常:年龄越界异常
class AgeOutOfBoundsException extends RuntimeException {
    public AgeOutOfBoundsException() {
    }

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

JavaBean类

public class Student {
    private String name;
    private int age;


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        //限制姓名长度应在3-10位
        if (name.length() < 3 || name.length() > 10) {
            throw new NameFormatException("姓名长度应在3-10位");
        }
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        //限制年龄范围应在18-25岁
        if (age < 18 || age > 25) {
            throw new AgeOutOfBoundsException("年龄范围应在18-25岁");
        }
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

测试类

public class ExceptionTest {
    public static void main(String[] args) {
        Student student = new Student();
        Scanner sc = new Scanner(System.in);

        while (true) {
            try {
                System.out.println("请输入姓名:");
                String name = sc.nextLine();
                student.setName(name);
                System.out.println("请输入年龄:");
                String ageStr = sc.nextLine();
                int age = Integer.parseInt(ageStr);
                student.setAge(age);
                //只要执行到这一步,说明没有任何异常,跳出循环。
                break;
            } catch (NumberFormatException e) {
                e.printStackTrace();
            } catch (NameFormatException e) {
                e.printStackTrace();
            } catch (AgeOutOfBoundsException e) {
                e.printStackTrace();
            }
        }

        System.out.println(student);
    }
}

运行测试:

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 编程中,异常处理是非常重要的一个概念。当程序运行时,由于某些原因,可能会出现一些意料之外的错误或异常,这时需要对这些异常进行处理,否则程序可能会崩溃或产生不可预知的结果。 Java 提供了异常处理机制来处理这些异常,使用 try-catch 语句块来捕获和处理异常。try-catch 语句块分为两部分,try 块和 catch 块。try 块用来包含可能会引发异常的代码,而 catch 块用来处理异常。 当 try 块中的代码引发异常时,程序将会跳转到 catch 块中,并执行 catch 块中的代码来处理异常。在 catch 块中,可以通过捕获的异常类型来判断异常的种类,并做出相应的处理。 除了 try-catch 语句块外,Java 还提供了 finally 块来执行一些无论是否发生异常都需要执行的代码,比如释放资源等。 在 Java 中,异常分为两种:受检异常和非受检异常。受检异常是指必须在方法中明确处理的异常,比如 IOException 和 SQLException 等,而非受检异常则是指 RuntimeException 及其子类抛出的异常,比如 NullPointerException 和 ArrayIndexOutOfBoundsException 等。非受检异常通常是由程序中的逻辑错误导致的,因此无法预测和避免,但可以通过编写健壮的代码来减少这些异常的发生。 总之,在 Java 中,异常处理是非常重要的,它可以让程序更加健壮,更加可靠。因此,在编写 Java 程序时,应该养成良好的异常处理习惯。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值