Java 异常处理机制

Posted 猪八戒1.0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 异常处理机制相关的知识,希望对你有一定的参考价值。

目录

1.1Java 异常的概念与分类

1.1.1引入异常(数组越界)

1.1.2 Throwable

1.2 Java 异常捕获处理

1.3 异常处理 finally 语句使用

1.3.1 引入 finally 关键字

1.3.2 采用 finally 释放 Scanner 资源

1.3 Java 异常抛出处理

1.3.1 主动抛出异常

1.4 自定义异常类

1.5 综合案例:


1.1Java 异常的概念与分类

1.1.1引入异常(数组越界)

public class TestException 
    /**
     * 会出现 ArrayIndexOutOfBoundsException 异常
     */
    public void method1()
        String names[] = "小凳子", "小椅子", "小桌子";
        for (int i = 0; i < 4; i++) 
            System.out.println(names[i]);
        
        System.out.println("over");
    

    public static void main(String[] args) 
        TestException te = new TestException();
        te.method1();
    

1.1.2 Throwable

异常的最高父类是 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()返回对象的简短描述信息

Exception 类也有很多的子类,提供我们使用

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

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

1.2 Java 异常捕获处理

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

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

程序员通常只能处理违例 (Exception) ,而对错误 (Error) 无能为力。

异常处理机制中已经明确提出需要有代码来处理该异常,而出现异常,系统会自动生成一个异常类对象,这个异常对象是系统生成的,我们无须创建。

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

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

try 语句块:

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

catch 语句块:

  • 在 catch 语句块中是对异常对象进行处理的代码。
  • 每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象。
  • 通过 getMessage( ) 获取异常信息或 printStackTrace( ) 跟踪异常信息。
public class TestException 
    public void method1()
        String names[] = "小凳子", "小椅子", "小桌子";
        try 
            for (int i = 0; i < 4; i++) 
                System.out.println(names[i]);
            
            System.out.println("over");
        
        catch (ArrayIndexOutOfBoundsException e)
            System.out.print(e);
        

    
    public static void main(String[] args) 
        TestException te = new TestException();
        te.method1();
    

        出现异常,系统自动生成 ArrayIndexOutOfBoundsException 异常对象,交给运行时系统,系统去找处理的代码,当匹配到有 catch 的异常是 ArrayIndexOutOfBoundsException 时,就进入 catch 语句块,本次处理方式是打印异常对象,就是调用 toString() 方法返回对象的简短描述信息。使用 catch 语句块,异常对象捕获到一个匹配的 catch ,将不会再考虑其他了

ArithmeticException 数学计算异常(如出现分母为0)

NumberFormatException 数字格式异常(如输入数字时输入字符串)

InputMismatchException 输入不匹配

public class TestException 
    public void method2(String a,String b)
        double c = 0.0;
        try
            c = Integer.parseInt(a) / Integer.parseInt(b);
        catch(ArithmeticException e)
            System.out.println(e.getMessage());
        catch(NumberFormatException e)
            e.printStackTrace();
        
        System.out.println(c);
    
    public static void main(String[] args) 
        TestException te = new TestException();
        te.method2(args[0], args[1]);
    

最简化的书写方式,也可以这样写,它们都是 Exception 子类。

public class TestException 

    public void method3(String a,String b)
        double c = 0.0;
        try
            c = Integer.parseInt(a) / Integer.parseInt(b);
        catch(Exception e)
            System.out.println(e.getMessage());
        
        System.out.println(c);
    
    public static void main(String[] args) 
        TestException te = new TestException();
        te.method3(args[0], args[1]);
    

1.3 异常处理 finally 语句使用

1.3.1 引入 finally 关键字

之前我们学过的是 final 关键字,而现在需要来学习的 finally 关键字。

finally 关键字主要是和捕获异常的 try-catch 语句一起使用,放在 finally 语句块中的内容表示无条件执行的部分,也就是说不管程序有异常或没有异常都需要执行的部分

try
    // 可能产生异常的代码
catch( ExceptionClass1 e )
    // 当产生 ExceptionName1 类型异常时的处置措施
catch( ExceptionClass2 e )
    // 当产生ExceptionName2型异常时的处置措施
finally
    // 无条件执行的语句

运行时异常:编译的时候都是没有问题,但是运行时会出现异常

RuntimeException 是所有运行时异常的父类,API 如下图:

 而实际中有许多都是在编译时会提示需要进行异常处理,否则编译将不能通过。如:

我们可以通过 SimpleDateFormat 类调用 format() 方法来进行日期格式化操作

同时对于以上的代码,我们应该完成转换操作之后,将 SimpleDateFormat 资源进行释放。你就应该要想到的是 finally 语句,无论是否有异常存在,都应该执行的部分,因为它是无条件执行语句块。

企业面试时,经常会提到的问题:final、finally 和 finalize 的区别是什么?

  • final 关键字,是用来修饰属性、方法、类的。
  • finally 关键字,可以配合异常处理,进行无条件执行操作。
  • finalize 不是关键字,是 Object 类中的一个方法,是 Java 垃圾回收机制中进行资源释放的方法。

1.3.2 采用 finally 释放 Scanner 资源

为了避免造成大量的内存占据,我们需要将不在使用的内存空间得到及时的释放。方法结束后,Scanner 对象虽然会进入被回收的队列中,但不是立刻回收,而这会给计算机带来一定的负担。

import java.util.InputMismatchException;
import java.util.Scanner;

public class DivideApples 
    //苹果数
    int appleNum = 0;
    //学生数
    int stuNum = 0;
    public void divide() 
        System.out.println("**_现在给孩子们分苹果_**");
        Scanner input = new Scanner(System.in);
        try
            System.out.print("请输入桌子上有几个苹果:");
            appleNum = input.nextInt();
            System.out.print("请输入班上有几个孩子:");
            stuNum = input.nextInt();
            System.out.println("班上每个孩子分得多少苹果:" + appleNum / stuNum);
            System.out.println("孩子们非常开心!");
        
        catch (InputMismatchException e)
            System.out.print("苹果数量和孩子人数必须为整数值!");
        catch (ArithmeticException e)
        System.out.print("孩子人数不能为零!");
        finally 
            input.close();
            System.out.print("Scanner 对象进行释放");
        
    
    public static void main(String[] args) 
        new DivideApples().divide();
    

1.3 Java 异常抛出处理

异常的另一种处理方式,便是抛出异常,需要使用关键字 throws 。throws 抛出的异常可以是 0 或多个,也就是说·可以抛出 1 个,或多个。

知道会出现错误直接抛出不处理,由调用该方法的对象处理(try-catch),也可以提醒别人。

public void method2(String a,String b) throws ArithmeticException, NumberFormatException
    double c = Integer.parseInt(a) / Integer.parseInt(b);
    System.out.println(c);

当然你也可以采用最简化的抛出方式:

public void method3(String a,String b) throws Exception
        double c = Integer.parseInt(a) / Integer.parseInt(b);
        System.out.println(c);

直接抛出 Exception,就不用一个个的抛出了,当然从规范的角度来说,在企业项目中,还是需要确切到具体的异常类。


public class TestException4
    /**
     * 采用异常抛出处理方式进行数组下标越界处理
     * @throws ArrayIndexOutOfBoundsException 数组下标越界异常
     */
    public void method1() throws ArrayIndexOutOfBoundsException
        String names[] = "小凳子", "小椅子", "小桌子";
        for (int i = 0; i < 4; i++) 
            System.out.println(names[i]);
        
        System.out.println("over");
    

    /**
     * 采用异常抛出处理方式进行数学计算和数字格式化处理
     * @param a 命令行参数
     * @param b 命令行参数
     * @throws ArithmeticException 数学计算异常
     * @throws NumberFormatException 数字格式化异常
     */
    public void method2(String a,String b) throws ArithmeticException, NumberFormatException
        double c = Integer.parseInt(a) / Integer.parseInt(b);
        System.out.println(c);
    

    /**
     * 采用最简化的方法抛出异常处理
     * @param a 命令行参数
     * @param b 命令行参数
     * @throws Exception 包含了所有的异常
     */
    public void method3(String a,String b) throws Exception
        double c = Integer.parseInt(a) / Integer.parseInt(b);
        System.out.println(c);
    

    public static void main(String[] args) 
        TestException4 te = new TestException4();
        // 调用 method1 方法进行异常处理
        try
            te.method1();
        catch(ArrayIndexOutOfBoundsException e)
            System.out.println(e);
        
        // 调用 method2 方法进行异常处理
        try 
            te.method2(args[0], args[1]);
         catch (ArithmeticException e) 
            e.printStackTrace();
         catch (NumberFormatException e)
            e.printStackTrace();
        
        // 调用 method3 方法进行异常处理
        try 
            te.method3(args[0], args[1]);
         catch (Exception e) 
            e.printStackTrace();
        
    

1.3.1 主动抛出异常

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

主动抛出异常,其实是希望提供更明确的异常提示信息。

语法结构:

throw new 异常类(message);

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

/**
 * 使用 throw 主动抛出异常
 */
public class TestException5 
    /**
     * throw 和 throws 配置使用
     * @throws Exception 异常类
     */
    public void method1() throws Exception
        String names[] = "小凳子", "小椅子", "小桌子";
        for (int i = 0; i < 4; i++) 
            if(i > names.length-1)
                throw new ArrayIndexOutOfBoundsException("数组下标越界异常:数组元素提取下标为 " + i + ",超出数组取值范围!");
            
            System.out.println(names[i]);
        
        System.out.println("over");
    
    /**
     * throw 和 try-catch 配置使用
     * @param a 命令行参数
     * @param b 命令行参数
     */
    public void method2(String a,String b)
        double c = 0.0;
        try
            int a1 = Integer.parseInt(a);
            if(a1 == 0)
                throw new RuntimeException("第一个参数为 0 ,除法计算永远都是零");
            
            int b1 = Integer.parseInt(b);
            if(b1 == 0)
                throw new ArithmeticException("除数不能为零");
            
            c = a1 / b1;
        catch(ArithmeticException e)
            System.out.println(e);
        catch(NumberFormatException e)
            e.printStackTrace();
        catch(RuntimeException e)
            System.out.println(e.getMessage());
        
        System.out.println(c);
    

    public static void main(String[] args) 
        TestException5 te = new TestException5();
        // 调用 mehtod1 方法进行异常处理
        try 
            te.method1();
         catch (Exception e) 
            System.out.println(e.getMessage());
        
        // 调用 method2 方法
        te.method2(args[0], args[1]);
    

方法重写的要求吗?

  1. 方法名、返回类型、参数列表必须相同。
  2. 方法的修饰符范围可以相同或更大。

我们可以发现,throws 是不是在方法上声明的,对吧 ~ 🙂

那么方法的重写需要加上第三个要求,抛出的异常要么和父类一样,或是父类抛出异常的子类,但是不能是抛出异常的父类。

父类 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

  1. 学会使用 throw 和 throws 关键字进行异常操作。
  2. throws 在方法声明的同时抛出异常,可以到调用方法时再进行处理。
  3. throw 主动抛出异常对象,可以传递更为确切的异常信息。
  4. 当子类重写父类方法时,需要考虑抛出的异常类,可以相同或是子类,但是不能更大。


1.4 自定义异常类

我们简单的复习一下:

异常处理机制,当发现异常,系统将自动生成异常类对象,然后抛出异常交给运行时系统,系统查找是否有可以处理该异常的代码,找到匹配的 catch ,将异常交给它处理即可,如果找不到处理的代码,那么程序将被迫停止。

异常处理的两种方式:

  • 捕获异常,采用 try-catch 语句。
  • 抛出异常,采用 throws 在方法声明中抛出。

当 API 中提供的异常类不能满足你的需求时,需要自定义异常类。

还有一种情况是,在企业项目中,可以更明确异常位置和信息,我们需要自定义异常类。

自定义异常类,其实很简单,只需满足以下两个要求即可:

  1. 声明一个类需要继承 Exception 或是它的子类。
  2. 提供至少 2 个构造方法,一个无参构造器,一个带参构造器,参数需要包含传递的异常信息。

语法结构:

[public] class 类名 extends Exception 或是 Exception 子类
      // 无参构造器
    public 类名()
      // 不传递默认异常信息也是可以的
      super("传递默认的异常信息");
    

      public 类名(String messag[, 参数类型 参数名 ...])
      // 通过父类带一个参数的构造器,传递异常信息
      super(message);
      // 其他数据传递操作,和之前要求的是一样的
    

注意:对于异常类的类名,一般我们在命名时,可以根据作用功能进行命名,后缀加上 Exception,如:「AgeException」、「PersonException」等等。

判断数组是否为空,如果为空给出异常提示信息。

class ArrayIsNullException extends Exception
      // 无参构造器
    public ArrayIsNullException()
        super("ArrayIsNullException: 数组长度为 0");
    
      // 带参构造器
    public ArrayIsNullException(String message)
        super(message);
    

使用自定义异常类:

public int method1(int[] arrays, int index)
    if(arrays.length == 0)
      throw new ArrayIsNullException();
    
    return arrays[index];

编译后报错提示我们必须对其进行捕获或声明以便抛出。

修改后:

/**
 * 自定义异常类的使用
 */
public class TestException6 

   
    public void method1(int[] arrays, int index) throws ArrayIsNullException 
        if (arrays.length == 0) 
            //throw new ArrayIsNullException();
            throw new ArrayIsNullException("数组为空,提取不到下标 " + index + " 的数组元素。");
        
        System.out.println(arrays[index]);
    

    public static void main(String[] args) 
        TestException6 te = new TestException6();
        try 
            te.method1(new int[] , 2);
         catch (ArrayIsNullException e) 
            System.out.println(e.getMessage());
        
    


// 自定义异常类
class ArrayIsNullException extends Exception

    /**
     * 无参构造器,传递默认异常信息
     */
    public ArrayIsNullException()
        super("ArrayIsNullException: 数组长度为 0");
    

    /**
     * 带异常信息的构造器
     * @param message 异常信息
     */
    public ArrayIsNullException(String message)
        super(message);
    

本实验主要学习自定义异常类,并结合 try-catch 语句和 throws 抛出异常处理。

创建异常类,只需满足以下两个要求:

  1. 声明一个类需要继承 Exception 或是它的子类。
  2. 提供至少 2 个构造方法,一个无参构造器,一个带参构造器,参数需要包含传递的异常信息。

1.5 综合案例:

姓名要求输入不少于 1 个字符且不超过 20 个字符,否则应该抛出异常;年龄只能输入为 18 ~ 35 的 int 类型数字,否则应该抛出异常;性别只能输入 1 或 2 进行选择,否则应该抛出异常,抛出异常后应提示“请重新输入”,并且可以重新输入当前信息。

import java.util.Scanner;

public class Registration extends Exception

    public Registration()  
        super();
    

    public Registration(String message) 
        super(message);
    

    public static Scanner input = new Scanner(System.in);

    public static void main(String[] args) 

        System.out.println("请输入您的姓名(不小于 1 个字符且不超过 20 个字符):");
        String name = new String();
        do
            try 
                name = inputName();
             catch (Registration e) 
                System.out.println(e.getMessage());
            
        while ((name.length()<1)||(name.length()>20));

        System.out.println("请输入您的年龄(18~35):");
        int age = 0;
        do
            try 
                age = inputAge();
             catch (Registration e) 
                System.out.println(e.getMessage());
            
        while (age<18||age>35);


        System.out.println("请选择您的性别:1. 男    2. 女");
        String gender = new String();
        do
            try 
                gender = setGender();
             catch (Registration e) 
                System.out.println(e.getMessage());
            
        while (!gender.equals("男")&&!gender.equals("女"));


        System.out.println("您的报名信息为: 姓名:" + name + ";年龄:" + age + ";性别:" + gender);


    /**
     * 输入姓名的方法
     */
    public static String inputName() throws Registration 
        String name=null;

        name=input.next();
        if(name.length()>=1&&name.length()<=20)
            return name;
        
        else 
            throw new Registration("你输入的姓名长度不符合要求,请重新输入:");
        
    

    /**
     * 输入年龄的方法
     */
    public static int inputAge() throws Registration 
        // 补充代码
        int age=input.nextInt();
        if(age>=18&&age<=35)
            return age;
        
        else 
            throw new Registration("年龄设置为"+age+"不符合要求,请重新输入:");
        
    

    /**
     * 选择性别的方法
     */
    public static String setGender() throws Registration 
        // 补充代码
        String gender=input.next();
        if(gender.equals("1"))
            return "男";
        
        else if(gender.equals("2"))
            return "女";
        
        else 
            throw new Registration("请输入1或2选择你的性别,请重新输入:");
        
    

    // 内部异常类
    // 补充代码

以上是关于Java 异常处理机制的主要内容,如果未能解决你的问题,请参考以下文章

很多程序员多年都没掌握的异常处理技巧和原则

Java中的异常处理try catch(第八周课堂示例总结)

JAVA系列关于异常的一些事

第八周动手动脑

JAVA中的异常处理机制的原理

Java中的异常处理机制的简单原理和应用。