关于java异常

77 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情

引入

异常,简单来说就是程序出问题了。那么编译代码提示的错误信息,就是异常吗?

之前我们所看到的编译错误,大部分只是代码中存在语法错误的提示信息,而不是异常。

慢慢的随着学习的深入,我们后面会看到,程序中有些可能会出现的异常,在编译时会给出提示信息,让你提前做好异常处理。

异常的最高父类是 Throwable,在 java.lang 包下。

Throwable 类的构造方法主要有:

  • public Throwable()

    构造一个对象,其错误信息串为 null。

  • public Throwable(String message)

    构造一个对象,其错误信息串为 message。

Throwable 类的方法主要有:

方法说明
public String getMessage()返回对象的错误信息
public void printStackTrace()输出对象的跟踪信息到标准错误输出流
public void printStackTrace(PrintStream s)输出对象的跟踪信息到输出流 s
public String toString()返回对象的简短描述信息

Throwable 类的子类有 Error和 Exception。

图片描述

Error 错误并不能通过程序代码来解决问题,而 Exception 违例基本是程序员在编写代码时所造成的问题,所以往往我们会将 Error 设为天灾,Exception 设为人祸。 😲

所以我们之后所书写的代码中,基本都是在处理 Exception 问题。Exception 类也有很多的子类,提供我们使用,如下图:

图片描述

下面我们通过一个实例来理解异常

假设有m个苹果,我们把他分给h个小朋友。 一般会这样写

// 苹果数 
int apple_Num = 0; 
// 学生数 
int student_Num = 0; 
System.out.println("**_现在给孩子们分苹果_**"); 
Scanner input = new Scanner(System.in); 
System.out.print("请输入桌子上有几个苹果:"); 
apple_Num = input.nextInt();
System.out.print("请输入班上有几个孩子:"); 
student_Num = input.nextInt();
System.out.println("班上每个孩子分得多少苹果:" + apple_Num / student_Num);

但是这样有一个问题,如果孩子个数为0,就会出现除数为0情况。

马上我们就想到了,可以采用 while 循环的方式进行判断,如果用户输入的孩子数为 0,则要求用户继续输入,通过这种方式,就解决了除数为 0 的异常产生的问题

while (student_Num == 0) {
    System.out.print("请输入班上有几个孩子(孩子数不能为 0):");
    student_Num = input.nextInt();
}
System.out.println("班上每个孩子分得多少苹果:" + apple_Num / student_Num);

采用判断语句的方式进行异常的处理,首先必须意识到哪些地方可能出现异常,在可能出现异常的地方加入判断语句和处理代码。这种处理方式对程序员的要求高,因为开发者很难列举出所有的异常发生情况,而且代码量大,程序结构相对混乱。对于此类情况,一种较好的解决方案,就是使用异常处理机制。

异常处理捕获方式

Java 的异常处理机制中仍体现了面向对象的思想。

  • Java 程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给 Java 运行时系统,这个过程称为抛出异常
  • 当 Java 运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这一过程称为捕获异常
  • 如果 Java 运行时系统找不到可以捕获异常的方法,则运行时系统将终止,相应的 Java 程序也将退出。

程序员通常只能处理Exception ,而对Error无能为力。

我们来看一下捕获异常的基本结构:

try{
  // 有异常出现的语句,无异常语句也可以放在这个语句块中
}catch(ExceptionClass e){
  // 当产生 ExceptionClass 类型异常时的处置代码
}[catch(ExceptionClass2 e){
  // 当产生 ExceptionClass2 类型异常时的处置代码
}catch(ExceptionClassN e){
  // 当产生 ExceptionClassN 类型异常时的处置代码
}]

从结构中我们可以看到,try 和 catch 是关键字,用来处理异常的。该结构中,catch 语句块中一定不要空着,啥都不写,这是一种很危险的情况,如果空着,表示捕获异常,而不做任何处理。

try 语句块:

  • 将可能出现异常的代码都放在 try 代码块中。
  • try 语句块中发现异常,剩下的语句将不再执行。

catch 语句块:

  • 在 catch 语句块中是对异常对象进行处理的代码。
  • 每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象。
  • 通过 getMessage( ) 获取异常信息或 printStackTrace( ) 跟踪异常信息。

那么上面的小朋友案例我们可以写成

try {
           System.out.print("请输入桌子上有几个苹果:");
           apple_Num = input.nextInt();
            System.out.print("请输入班上有几个孩子:");
            student_Num = input.nextInt();
           System.out.println("班上每个孩子分得多少苹果:" + apple_Num / student_Num);
       }catch (Exception e){
           System.out.println("孩子不能为0");
           return;
       }

这是最简化的书写方式,都采用 Exception 异常类捕获。

⭐ 注意:当使用多个 catch 进行异常捕获时,需要考虑前后顺序,也就是说需要先捕获子类异常,再捕获父类异常,不相关的异常类没有关系。(如果有多个异常,最后一个catch才是捕获Exception)

catch(Exception e){
            System.out.println(e.getMessage());
        }catch(ArithmeticException e){
            e.printStackTrace();
        }catch(NumberFormatException e){
            e.printStackTrace();
        }
        System.out.println(c);

那么 ArithmeticException 和 NumberFormatException 异常捕获的 catch 语句块将永远都不会被执行,当然程序编译时就会给出错误提示信息。

异常处理抛出方式

异常的另一种处理方式,便是抛出异常,需要使用关键字 throws 。

异常抛出处理,主要是在方法声明部分使用。

语法格式:

[public|protected|private] 返回类型 方法名(参数列表) throws 异常类[, 异常类2, ..., 异常类n]{
   // 方法体
}

throws 抛出的异常可以是 0 或多个,也就是说声明方法时可以不抛出异常,也可以抛出 1 个,或多个。

在定义一个方法的时候,如果并不能确定如何处理其中可能出现的异常,可以不在方法中对异常进行处理,而是将可能发生的异常让这个方法的调用者来处理

比如我们之前小朋友的案例

  public void method() throws  Exception{
      。。。
            System.out.print("请输入班上有几个孩子:");
            student_Num = input.nextInt();
           System.out.println("班上每个孩子分得多少苹果:" + apple_Num / student_Num);
       }
        System.out.println("孩子们非常开心!");
    }

接着,我们需要来强调的是主动抛出异常,使用关键字 throw。

🤔 那么什么是主动抛出异常呢?

之前讲到异常处理机制,当系统发现有异常,会自动产生一个异常对象,然后交给运行时系统,这叫抛出异常;主动抛出异常,那就是不需要系统生成异常对象,而是我们在编写代码时,对于可能会出现异常的部分,自己创建异常对象将它抛出,这就是主动抛出异常。

来看看语法结构:

throw new 异常类(message);

⭐ 注意:这里是 throw 而不是 throws,有很多人会搞混 throw 和 throws,你只需要记住一点,throw 关键字后只能抛出一个确切的异常类对象,而 throws 后可以抛出多个异常类,而非 new 的对象。

那么异常方法的重写需要方法重新要求+ 抛出的异常要么和父类一样,或是父类抛出异常的子类,但是不能是抛出异常的父类。

父类 ClassA 中的 method() 方法:

public void method() throws ArithmeticException, NumberFormatException{

}

子类 ClassB 中重写父类 method() 方法:

可以只抛出父类中的一个异常类。

public void method() throws ArithmeticException{

}

或是

public void method() throws NumberFormatException{

}

或是

public void method() throws ArithmeticException,NumberFormatException{

}

不抛出异常类也是可以的。

public void method(){

}

但是抛出的异常不能大于父类抛出的异常,则是错误的。

public void method() throws Exception{

}