Java 异常处理

在这里插入图片描述

达者为先  师者之意


1 异常简介

Java 中的异常又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。为了能够及时有效地处理程序中的运行错误,必须使用异常类,这可以让程序具有极好的容错性且更加健壮。

在 Java 中一个异常的产生,主要有如下三种原因:

  1. Java 内部错误发生异常,Java 虚拟机产生的异常。
  2. 编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。
  3. 通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息。

Java 通过面向对象的方法来处理异常。在一个方法的运行过程中,如果发生了异常,则这个方法会产生代表该异常的一个对象,并把它交给运行时的系统,运行时系统寻找相应的代码来处理这一异常。

我们把生成异常对象,并把它提交给运行时系统的过程称为拋出(throw)异常。运行时系统在方法的调用栈中查找,直到找到能够处理该类型异常的对象,这一个过程称为捕获(catch)异常。

为了更好地理解什么是异常,下面来看一段非常简单的 Java 程序。下面的示例代码实现了允许用户输入 1~3 以内的整数,其他情况提示输入错误。

import java.util.Scanner;
public class Test01 {
    public static void main(String[] args) {
        System.out.println("请输入您的选择:(1~3 之间的整数)");
        Scanner input = new Scanner(System.in);
        int num = input.nextInt();
        switch (num) {
        case 1:
            System.out.println("one");
            break;
        case 2:
            System.out.println("two");
            break;
        case 3:
            System.out.println("three");
            break;
        default:
            System.out.println("error");
            break;
        }
    }
}

正常情况下,用户会按照系统的提示输入 1~3 之间的数字。但是,如果用户没有按要求进行输入,例如输入了一个字母“a”,则程序在运行时将会发生异常,运行结果如下所示。

请输入您的选择:(1~3 之间的整数)
a
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Unknown Source)
        at java.util.Scanner.next(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at text.text.main(text.java:11)

2 异常类型

为了能够及时有效地处理程序中的运行错误,Java 专门引入了异常类。在 Java 中所有异常类型都是内置类 java.lang.Throwable 类的子类,即 Throwable 位于异常类层次结构的顶层。Throwable 类下有两个异常分支 Exception 和 Error,如下图所示。

在这里插入图片描述

由图 可以知道,Throwable 类是所有异常和错误的超类,下面有 Error 和 Exception 两个子类分别表示错误和异常。其中异常类 Exception 又分为运行时异常和非运行时异常,这两种异常有很大的区别,也称为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。

  • Exception 类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。
  • Error 定义了在通常环境下不希望被程序捕获的异常。一般指的是 JVM 错误,如堆栈溢出。

本节不讨论关于 Error 类型的异常处理,因为它们通常是灾难性的致命错误,不是程序可以控制的。接下来将讨论 Exception 类型的异常处理。

运行时异常都是 RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。

非运行时异常是指 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常(一般情况下不自定义检查异常)。

表 1 和表 2 分别列出了 java.lang 中定义的运行时异常和非运行时异常的类型及作用。
在这里插入图片描述

3 异常处理机制

Java 的异常处理通过 5 个关键字来实现:try、catch、throw、throws 和 finally。try catch 语句用于捕获并处理异常,finally 语句用于在任何情况下(除特殊情况外)都必须执行的代码,throw 语句用于拋出异常,throws 语句用于声明可能会出现的异常。

本节先主要介绍异常处理的机制及基本的语句结构。

Java 的异常处理机制提供了一种结构性和控制性的方式来处理程序执行期间发生的事件。异常处理的机制如下:
在方法中用 try catch 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个异常。
对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 语句拋出异常,即由上层的调用方法来处理。

以下代码是异常处理程序的基本结构:

try {
    逻辑程序块
} catch(ExceptionType1 e) {
    处理代码块1
} catch (ExceptionType2 e) {
    处理代码块2
    throw(e);    // 再抛出这个"异常"
} finally {
    释放资源代码块
}

4 try catch语句详解

4.1 捕获异常

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。
在 Java 中通常采用 try catch 语句来捕获异常并处理。语法格式如下:

try {
    // 可能发生异常的语句
} catch(ExceptionType e) {
    // 处理异常语句
}

在以上语法中,把可能引发异常的语句封装在 try 语句块中,用以捕获可能发生的异常。catch 后的( )里放匹配的异常类,指明 catch 语句可以处理的异常类型,发生异常时产生异常类的实例化对象。

如果 try 语句块中发生异常,那么一个相应的异常对象就会被拋出,然后 catch 语句就会依据所拋出异常对象的类型进行捕获,并处理。处理之后,程序会跳过 try 语句块中剩余的语句,转到 catch 语句块后面的第一条语句开始执行。

如果 try 语句块中没有异常发生,那么 try 块正常结束,后面的 catch 语句块被跳过,程序将从 catch 语句块后的第一条语句开始执行。

注意:try…catch 与 if…else 不一样,try 后面的花括号{ }不可以省略,即使 try 块里只有一行代码,也不可省略这个花括号。与之类似的是,catch 块后的花括号{ }也不可以省略。另外,try 块里声明的变量只是代码块内的局部变量,它只在 try 块内有效,其它地方不能访问该变量。

在上面语法的处理代码块 1 中,可以使用以下 3 个方法输出相应的异常信息。

  • printStackTrace() 方法:指出异常的类型、性质、栈层次及出现在程序中的位置。
  • getMessage() 方法:输出错误的性质。
  • toString() 方法:给出异常的类型与性质。

实例
下面的例子中声明有两个元素的一个数组,当代码试图访问数组的第四个元素的时候就会抛出一个异常。

ExcepTest.java 文件代码:

// 文件名 : ExcepTest.java
import java.io.*;
public class ExcepTest{
 
   public static void main(String args[]){
      try{
         int a[] = new int[2];
         System.out.println("Access element three :" + a[3]);
      }catch(ArrayIndexOutOfBoundsException e){
         System.out.println("Exception thrown  :" + e);
      }
      System.out.println("Out of the block");
   }
}
/*
以上代码编译运行输出结果如下:
Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block
*/

编写一个录入学生姓名、年龄和性别的程序,要求能捕捉年龄不为数字时的异常。在这里使用 try catch 语句来实现,具体代码如下:

import java.util.Scanner;
public class Test02 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("---------学生信息录入---------------");
        String name = ""; // 获取学生姓名
        int age = 0; // 获取学生年龄
        String sex = ""; // 获取学生性别
        try {
            System.out.println("请输入学生姓名:");
            name = scanner.next();
            System.out.println("请输入学生年龄:");
            age = scanner.nextInt();
            System.out.println("请输入学生性别:");
            sex = scanner.next();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("输入有误!");
        }
        System.out.println("姓名:" + name);
        System.out.println("年龄:" + age);
    }
}

上述代码在 main() 方法中使用 try catch 语句来捕获异常,将可能发生异常的age = scanner.nextlnt();代码放在了 try 块中,在 catch 语句中指定捕获的异常类型为 Exception,并调用异常对象的 printStackTrace() 方法输出异常信息。运行结果如下所示。

---------学生信息录入---------------
请输入学生姓名:
徐白
请输入学生年龄:
110a
java.util.InputMismatchException
    at java.util.Scanner.throwFor(Unknown Source)
    at java.util.Scanner.next(Unknown Source)
    at java.util.Scanner.nextInt(Unknown Source)
    at java.util.Scanner.nextInt(Unknown Source)
输入有误!
姓名:徐白
年龄:0
    at text.text.main(text.java:19)

4.2 多重catch语句

如果 try 代码块中有很多语句会发生异常,而且发生的异常种类又很多。那么可以在 try 后面跟有多个 catch 代码块。多 catch 代码块语法如下:

try {
    // 可能会发生异常的语句
} catch(ExceptionType e) {
    // 处理异常语句
} catch(ExceptionType e) {
    // 处理异常语句
} catch(ExceptionType e) {
    // 处理异常语句
...
}

在多个 catch 代码块的情况下,当一个 catch 代码块捕获到一个异常时,其它的 catch 代码块就不再进行匹配。

注意:当捕获的多个异常类之间存在父子关系时,捕获异常时一般先捕获子类,再捕获父类。所以子类异常必须在父类异常的前面,否则子类捕获不到。

public class Test03 {
    public static void main(String[] args) {
        Date date = readDate();
        System.out.println("读取的日期 = " + date);
    }
    public static Date readDate() {
        FileInputStream readfile = null;
        InputStreamReader ir = null;
        BufferedReader in = null;
        try {
            readfile = new FileInputStream("readme.txt");
            ir = new InputStreamReader(readfile);
            in = new BufferedReader(ir);
            // 读取文件中的一行数据
            String str = in.readLine();
            if (str == null) {
                return null;
            }
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            Date date = df.parse(str);
            return date;
        } catch (FileNotFoundException e) {
            System.out.println("处理FileNotFoundException...");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("处理IOException...");
            e.printStackTrace();
        } catch (ParseException e) {
            System.out.println("处理ParseException...");
            e.printStackTrace();
        }
        return null;
    }
}

上述代码通过 Java I/O(输入输出)流技术从文件 readme.txt 中读取字符串,然后解析成为日期。由于 Java I/O 技术还没有介绍,大家先不要关注 I/O 技术细节,只看调用它们时方法会发生的异常就可以了。

在 try 代码块中第 12 行代码调用 FileInputStream 构造方法可能会发生 FileNotFoundException 异常。第 16 行代码调用 BufferedReader 输入流的 readLine() 方法可能会发生 IOException 异常。FileNotFoundException 异常是 IOException 异常的子类,应该先捕获 FileNotFoundException 异常,见代码第 23 行;后捕获 IOException 异常,见代码第 26 行。

如果将 FileNotFoundException 和 IOException 捕获顺序调换,那么捕获 FileNotFoundException 异常代码块将永远不会进入,FileNotFoundException 异常处理永远不会执行。 上述代码第 29 行 ParseException 异常与 IOException 和 FileNotFoundException 异常没有父子关系,所以捕获 ParseException 异常位置可以随意放置。

5 try catch finally语句详解

在实际开发中,根据 try catch 语句的执行过程,try 语句块和 catch 语句块有可能不被完全执行,而有些处理代码则要求必须执行。例如,程序在 try 块里打开了一些物理资源(如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。
Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只回收堆内存中对象所占用的内存。

所以为了确保一定能回收 try 块中打开的物理资源,异常处理机制提供了 finally 代码块,并且 Java 7 之后提供了自动资源管理(Automatic Resource Management)技术。

  • finally 关键字用来创建在 try 代码块后面执行的代码块。
  • 无论是否发生异常,finally 代码块中的代码总会被执行。
  • 在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
  • finally 代码块出现在 catch 代码块最后

finally 语句可以与前面介绍的 try catch 语句块匹配使用,语法格式如下:

try {
    // 可能会发生异常的语句
} catch(ExceptionType e) {
    // 处理异常语句
} finally {
    // 清理代码块
}

对于以上格式,无论是否发生异常(除特殊情况外),finally 语句块中的代码都会被执行。此外,finally 语句也可以和 try 语句匹配使用,其语法格式如下:

try {
    // 逻辑代码块
} finally {
    // 清理代码块
}

使用 try-catch-finally 语句时需注意以下几点:

  • 异常处理语法结构中只有 try 块是必需的,也就是说,如果没有 try 块,则不能有后面的 catch 块和 finally 块;
  • catch 块和 finally 块都是可选的,但 catch 块和 finally 块至少出现其中之一,也可以同时出现;
  • 可以有多个 catch 块,捕获父类异常的 catch 块必须位于捕获子类异常的后面;
  • 不能只有 try 块,既没有 catch 块,也没有 finally 块;
  • 多个 catch 块必须位于 try 块之后,finally 块必须位于所有的 catch 块之后。
  • finally 与 try 语句块匹配的语法格式,此种情况会导致异常丢失,所以不常见。

一般情况下,无论是否有异常拋出,都会执行 finally 语句块中的语句,执行流程如下图所示。

在这里插入图片描述
try catch finally 语句块的执行情况可以细分为以下 3 种情况:

  1. 如果 try 代码块中没有拋出异常,则执行完 try 代码块之后直接执行 finally 代码块,然后执行 try catch finally 语句块之后的语句。
  2. 如果 try 代码块中拋出异常,并被 catch 子句捕捉,那么在拋出异常的地方终止 try 代码块的执行,转而执行相匹配的 catch 代码块,之后执行 finally 代码块。如果 finally 代码块中没有拋出异常,则继续执行 try catch finally 语句块之后的语句;如果 finally 代码块中拋出异常,则把该异常传递给该方法的调用者。
  3. 如果 try 代码块中拋出的异常没有被任何 catch 子句捕捉到,那么将直接执行 finally 代码块中的语句,并把该异常传递给该方法的调用者。

除非在 try 块、catch 块中调用了退出虚拟机的方法System.exit(int status),否则不管在 try 块或者 catch 块中执行怎样的代码,出现怎样的情况,异常处理的 finally 块总会执行。

通常情况下不在 finally 代码块中使用 return 或 throw 等导致方法终止的语句,否则将会导致 try 和 catch 代码块中的 return 和 throw 语句失效。

当 Windows 系统启动之后,即使不作任何操作,在关机时都会显示“谢谢使用”。下面编写 Java 程序使用 try catch finally 语句这个过程,具体代码如下:

import java.util.Scanner;
public class Test04 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("Windows 系统已启动!");
        String[] pros = { "记事本", "计算器", "浏览器" };
        try {
            // 循环输出pros数组中的元素
            for (int i = 0; i < pros.length; i++) {
                System.out.println(i + 1 + ":" + pros[i]);
            }
            System.out.println("是否运行程序:");
            String answer = input.next();
            if (answer.equals("y")) {
                System.out.println("请输入程序编号:");
                int no = input.nextInt();
                System.out.println("正在运行程序[" + pros[no - 1] + "]");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("谢谢使用!");
        }
    }
}

上述代码在 main() 方法中使用 try catch finally 语句模拟了系统的使用过程。当系统启动之后显示提示语,无论是否运行了程序,或者在运行程序时出现了意外,程序都将执行 finally 块中的语句,即显示“谢谢使用!”。输出时的结果如下所示。

Windows 系统已启动!
1:记事本
2:计算器
3:浏览器
是否运行程序:
y
请输入程序编号:
2
正在运行程序[计算器]
谢谢使用!
Windows 系统已启动!
1:记事本
2:计算器
3:浏览器
是否运行程序:
y
请输入程序编号:
5
谢谢使用!
java.lang.ArrayIndexOutOfBoundsException: 4
    at text.text.main(text.java:23)
Windows 系统已启动!
1:记事本
2:计算器
3:浏览器
是否运行程序:
asdfasd
谢谢使用!

再来个实例
ExcepTest.java 文件代码:

public class ExcepTest{
  public static void main(String args[]){
    int a[] = new int[2];
    try{
       System.out.println("Access element three :" + a[3]);
    }catch(ArrayIndexOutOfBoundsException e){
       System.out.println("Exception thrown  :" + e);
    }
    finally{
       a[0] = 6;
       System.out.println("First element value: " +a[0]);
       System.out.println("The finally statement is executed");
    }
  }
}

以上实例编译运行结果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed

注意下面事项:

  • catch 不能独立于 try 存在。
  • 在 try/catch 后面添加 finally 块并非强制性要求的。
  • try 代码后不能既没 catch 块也没 finally 块。
  • try, catch, finally 块之间不能添加任何代码。

6 finally与return的执行顺序

在 Java 的异常处理中,try、catch 和 finally 是按顺序执行的。如果 try 中没有异常,则顺序为 try→finally,如果 try 中有异常,则顺序为 try→catch→finally。但是当 try、catch、finally 中加入 return 之后,return 和 finally 的执行顺序让很多人混淆不清。下面来分别说明一下。

6.1 try 和 catch 中带有 return

  1. try 中带有 return
public class tryDemo {
    public static int show() {
        try {
            return 1;
        } finally {
            System.out.println("执行finally模块");
        }
    }
    public static void main(String args[]) {
        System.out.println(show());
    }
}

输出结果如下:

执行finally模块
1
  1. try 和 catch 中都带有 return
public class tryDemo {
    public static int show() {
        try {
            int a = 8 / 0;
            return 1;
        } catch (Exception e) {
            return 2;
        } finally {
            System.out.println("执行finally模块");
        }
    }
    public static void main(String args[]) {
        System.out.println(show());
    }
}

输出结果为:

执行finally模块
2

当 try 代码块或者 catch 代码块中有 return 时,finally 中的代码总会被执行,且 finally 语句 return 返回之前执行。

注意:可以使用编译器的 Debug 功能查看详细过程。

6.2 finally 中带有 return

public class tryDemo {
    public static int show() {
        try {
            int a = 8 / 0;
            return 1;
        } catch (Exception e) {
            return 2;
        } finally {
            System.out.println("执行finally模块");
            return 0;
        }
    }
    public static void main(String args[]) {
        System.out.println(show());
    }
}

输出结果如下:

执行finally模块
0

当 finally 有返回值时,会直接返回该值,不会去返回 try 代码块或者 catch 代码块中的返回值。

注意:finally 代码块中最好不要包含 return 语句,否则程序会提前退出。

6.3 finally 中改变返回值

下面先来看 try 代码块或者 catch 代码块中的返回值是普通变量时,代码如下:

public class tryDemo {
    public static int show() {
        int result = 0;
        try {
            return result;
        } finally {
            System.out.println("执行finally模块");
            result = 1;
        }
    }
    public static void main(String args[]) {
        System.out.println(show());
    }
}

输出结果为:

执行finally模块
0

由输出结果可以看出,在 finally 代码块中改变返回值并不会改变最后返回的内容。

当返回值类型是引用类型时,结果也是一样的,代码如下:

public class tryDemo {
    public static Object show() {
        Object obj = new Object();
        try {
            return obj;
        } finally {
            System.out.println("执行finally模块");
            obj = null;
        }
    }
    public static void main(String args[]) {
        System.out.println(show());
    }
}

输出结果为:

执行finally模块
java.lang.Object@15db9742

当 try 代码块或 catch 代码块中的 return 返回值类型为普通变量或引用变量时,即使在后面 finally 代码块中对返回值的变量重新赋值,也不会影响最后返回的值。

总结为以下几条:

  • 当 try 代码块和 catch 代码块中有 return 语句时,finally 仍然会被执行。
  • 执行 try 代码块或 catch 代码块中的 return 语句之前,都会先执行 finally 语句。
  • 无论在 finally 代码块中是否修改返回值,返回值都不会改变,仍然是执行 finally 代码块之前的值。
  • finally 代码块中的 return 语句一定会执行。

7 声明和抛出异常

Java 中的异常处理除了捕获异常和处理异常之外,还包括声明异常和拋出异常。实现声明和抛出异常的关键字非常相似,它们是 throws 和 throw。可以通过 throws 关键字在方法上声明该方法要拋出的异常,然后在方法内部通过 throw 拋出异常对象。本节详细介绍在 Java 中如何声明异常和拋出异常。

7.1 throws 声明异常

当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。使用 throws 声明的方法表示此方法不处理异常。

7.1.1 throws 具体格式如下:

returnType method_name(paramList) throws Exception 1,Exception2,{}

其中,returnType 表示返回值类型;method_name 表示方法名;paramList 表示参数列表;Exception 1,Exception2,… 表示异常类。

如果有多个异常类,它们之间用逗号分隔。这些异常类可以是方法中调用了可能拋出异常的方法而产生的异常,也可以是方法体中生成并拋出的异常。

使用 throws 声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。

实例
创建一个 readFile() 方法,该方法用于读取文件内容,在读取的过程中可能会产生 IOException 异常,但是在该方法中不做任何的处理,而将可能发生的异常交给调用者处理。在 main() 方法中使用 try catch 捕获异常,并输出异常信息。代码如下:

import java.io.FileInputStream;
import java.io.IOException;
public class Test04 {
    public void readFile() throws IOException {
        // 定义方法时声明异常
        FileInputStream file = new FileInputStream("read.txt"); // 创建 FileInputStream 实例对象
        int f;
        while ((f = file.read()) != -1) {
            System.out.println((char) f);
            f = file.read();
        }
        file.close();
    }
    public static void main(String[] args) {
        Throws t = new Test04();
        try {
            t.readFile(); // 调用 readFHe()方法
        } catch (IOException e) {
            // 捕获异常
            System.out.println(e);
        }
    }
}

以上代码,首先在定义 readFile() 方法时用 throws 关键字声明在该方法中可能产生的异常,然后在 main() 方法中调用 readFile() 方法,并使用 catch 语句捕获产生的异常。

7.1.2 方法重写时声明抛出异常的限制

使用 throws 声明抛出异常时有一个限制,是方法重写中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。看如下程序。

public class OverrideThrows {
    public void test() throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}
class Sub extends OverrideThrows {
    // 子类方法声明抛出了比父类方法更大的异常
    // 所以下面方法出错
    public void test() throws Exception {
    }
}

上面程序中 Sub 子类中的 test() 方法声明抛出 Exception,该 Exception 是其父类声明抛出异常 IOException 类的父类,这将导致程序无法通过编译。

所以在编写类继承代码时要注意,子类在重写父类带 throws 子句的方法时,子类方法声明中的 throws 子句不能出现父类对应方法的 throws 子句中没有的异常类型,因此 throws 子句可以限制子类的行为。也就是说,子类方法拋出的异常不能超过父类定义的范围。

7.2 throw 拋出异常

与 throws 不同的是,throw 语句用来直接拋出一个异常,后接一个可拋出的异常类对象,其语法格式如下:
throw ExceptionObject;

其中,ExceptionObject 必须是 Throwable 类或其子类的对象。如果是自定义异常类,也必须是 Throwable 的直接或间接子类。例如,以下语句在编译时将会产生语法错误:

throw new String("拋出异常");    // String类不是Throwable类的子类

当 throw 语句执行时,它后面的语句将不执行,此时程序转向调用者程序,寻找与之相匹配的 catch 语句,执行相应的异常处理程序。如果没有找到相匹配的 catch 语句,则再转向上一层的调用程序。这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。

throw 关键字不会单独使用,它的使用完全符合异常的处理机制,但是,一般来讲用户都在避免异常的产生,所以不会手工抛出一个新的异常类的实例,而往往会抛出程序中已经产生的异常类的实例。

实例
在某仓库管理系统中,要求管理员的用户名需要由 8 位以上的字母或者数字组成,不能含有其他的字符。当长度在 8 位以下时拋出异常,并显示异常信息;当字符含有非字母或者数字时,同样拋出异常,显示异常信息。代码如下:

import java.util.Scanner;
public class Test05 {
    public boolean validateUserName(String username) {
        boolean con = false;
        if (username.length() > 8) {
            // 判断用户名长度是否大于8位
            for (int i = 0; i < username.length(); i++) {
                char ch = username.charAt(i); // 获取每一位字符
                if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
                    con = true;
                } else {
                    con = false;
                    throw new IllegalArgumentException("用户名只能由字母和数字组成!");
                }
            }
        } else {
            throw new IllegalArgumentException("用户名长度必须大于 8 位!");
        }
        return con;
    }
    public static void main(String[] args) {
        Test05 te = new Test05();
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.next();
        try {
            boolean con = te.validateUserName(username);
            if (con) {
                System.out.println("用户名输入正确!");
            }
        } catch (IllegalArgumentException e) {
            System.out.println(e);
        }
    }
}

如上述代码,在 validateUserName() 方法中两处拋出了 IllegalArgumentException 异常,即当用户名字符含有非字母或者数字以及长度不够 8 位时。在 main() 方法中,调用了 validateUserName() 方法,并使用 catch 语句捕获该方法可能拋出的异常。

运行程序,当用户输入的用户名包含非字母或者数字的字符时,程序输出异常信息,如下所示。

请输入用户名:
administrator@#
java.lang.IllegalArgumentException: 用户名只能由字母和数字组成!

当用户输入的用户名长度不够 8 位时,程序同样会输出异常信息,如下所示。
请输入用户名:

admin
java.lang.IllegalArgumentException: 用户名长度必须大于 8 位!

throws 关键字和 throw 关键字在使用上的几点区别如下:

  • throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw 则是指拋出的一个具体的异常类型,执行 throw 则一定抛出了某种异常对象。
  • 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw 声明一个具体的异常信息。
  • throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。

8 自定义异常

如果 Java 提供的内置异常类型不能满足程序设计的需求,这时我们可以自己设计 Java 类库或框架,其中包括异常类型。实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类。

自定义异常的语法形式为:

<class><自定义异常名><extends><Exception>

在编码规范上,一般将自定义异常类的类名命名为 XXXException,其中 XXX 用来代表该异常的作用。

自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。

例如,以下代码创建一个名称为 IntegerRangeException 的自定义异常类:

class IntegerRangeException extends Exception {
    public IntegerRangeException() {
        super();
    }
    public IntegerRangeException(String s) {
        super(s);
    }
}

以上代码创建的自定义异常类 IntegerRangeException 类继承自 Exception 类,在该类中包含两个构造方法。

实例
编写一个程序,对会员注册时的年龄进行验证,即检测是否在 0~100 岁。

  1. 这里创建了一个异常类 MyException,并提供两个构造方法。MyException 类的实现代码如下:
public class MyException extends Exception {
    public MyException() {
        super();
    }
    public MyException(String str) {
        super(str);
    }
}
  1. 接着创建测试类,调用自定义异常类。代码实现如下:
import java.util.InputMismatchException;
import java.util.Scanner;
public class Test07 {
    public static void main(String[] args) {
        int age;
        Scanner input = new Scanner(System.in);
        System.out.println("请输入您的年龄:");
        try {
            age = input.nextInt();    // 获取年龄
            if(age < 0) {
                throw new MyException("您输入的年龄为负数!输入有误!");
            } else if(age > 100) {
                throw new MyException("您输入的年龄大于100!输入有误!");
            } else {
                System.out.println("您的年龄为:"+age);
            }
        } catch(InputMismatchException e1) {
            System.out.println("输入的年龄不是数字!");
        } catch(MyException e2) {
            System.out.println(e2.getMessage());
        }
    }
}
  1. 运行该程序,当用户输入的年龄为负数时,则拋出 MyException 自定义异常,执行第二个 catch 语句块中的代码,打印出异常信息。程序的运行结果如下所示。
请输入您的年龄:
-2
您输入的年龄为负数!输入有误!

当用户输入的年龄大于 100 时,也会拋出 MyException 自定义异常,同样会执行第二个 catch 语句块中的代码,打印出异常信息,如下所示。

请输入您的年龄:
110
您输入的年龄大于100!输入有误!

在该程序的主方法中,使用了 if…else if…else 语句结构判断用户输入的年龄是否为负数和大于 100 的数,如果是,则拋出自定义异常 MyException,调用自定义异常类 MyException 中的含有一个 String 类型的构造方法。在 catch 语句块中捕获该异常,并调用 getMessage() 方法输出异常信息。

提示:因为自定义异常继承自 Exception 类,因此自定义异常类中包含父类所有的属性和方法。

码字不易  求个三连

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大红烧肉

赠人玫瑰,手留余香。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值