Java基础知识(8)- Java 异常处理(二)| 自动资源管理、Java 7多异常捕获、自定义异常、异常跟踪栈
1. 自动资源管理(Automatic Resource Management)
在 try catch finally 语句使用文件资源,需要在 finally 块中关闭文件资源,代码如下:
1 public static void main(String[] args) { 2 FileInputStream fis = null; 3 try { 4 fis = new FileInputStream("a.txt"); 5 } catch (FileNotFoundException e) { 6 e.printStackTrace(); 7 } finally { 8 // 关闭磁盘文件,回收资源 9 if (fis != null) { 10 try { 11 fis.close(); 12 } catch (IOException e) { 13 e.printStackTrace(); 14 } 15 } 16 } 17 }
1) Java 7自动资源管理
上面代码中的 finally 代码块是不得不写的 “臃肿代码” ,为了解决这种问题,Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件,被称为自动资源管理(Automatic Resource Management)。
自动资源管理替代了 finally 代码块,并优化了代码结构和提高程序可读性。语法如下:
try (声明或初始化资源语句) {
// 可能会生成异常语句
} catch(Throwable e1){
// 处理异常e1
} catch(Throwable e2){
// 处理异常e1
} catch(Throwable eN){
// 处理异常eN
}
当 try 代码块结束时,自动释放资源。不再需要显式的调用 close() 方法,该形式也称为 “带资源的 try 语句”。
注意:
a) try 语句中声明的资源被隐式声明为 final,资源的作用局限于带资源的 try 语句。
b) 可以在一条 try 语句中声明或初始化多个资源,每个资源以;隔开即可。
c) 需要关闭的资源必须实现了 AutoCloseable 或 Closeable 接口。
Closeable 是 AutoCloseable 的子接口,Closeable 接口里的 close() 方法声明抛出了 IOException,因此它的实现类在实现 close() 方法时只能声明抛出 IOException 或其子类;AutoCloseable 接口里的 close() 方法声明抛出了 Exception,因此它的实现类在实现 close() 方法时可以声明抛出任何异常。
下面示范如何使用自动关闭资源的 try 语句。
1 public static void main(String[] args) throws IOException { 2 try ( 3 // 声明、初始化两个可关闭的资源 4 // try 语句会自动关闭这两个资源 5 BufferedReader br = new BufferedReader(new FileReader("d:\\temp\\test.java")); 6 PrintStream ps = new PrintStream(new FileOutputStream("d:\\temp\\data.txt"))) { 7 // 使用两个资源 8 System.out.println(br.readLine()); 9 ps.println("Java 7自动资源管理"); 10 } 11 }
上面程序中粗体字代码分别声明、初始化了两个 IO 流,BufferedReader 和 PrintStream 都实现了 Closeable 接口,并在 try 语句中进行了声明和初始化,所以 try 语句会自动关闭它们。
自动关闭资源的 try 语句相当于包含了隐式的 finally 块(这个 finally 块用于关闭资源),因此这个 try 语句可以既没有 catch 块,也没有 finally 块。
Java 7 几乎把所有的“资源类”(包括文件 IO 的各种类、JDBC 编程的 Connection 和 Statement 等接口)进行了改写,改写后的资源类都实现了 AutoCloseable 或 Closeable 接口。
如果程序需要,自动关闭资源的 try 语句后也可以带多个 catch 块和一个 finally 块。
2) Java 9增强的自动资源管理
Java 9 再次增强了这种 try 语句。Java 9 不要求在 try 后的圆括号内声明并创建资源,只需要自动关闭的资源有 final 修饰或者是有效的 final (effectively final),Java 9 允许将资源变量放在 try 后的圆括号内。
上面程序在 Java 9 中可改写为如下形式。
1 public static void main(String[] args) throws IOException { 2 // 有final修饰的资源 3 final BufferedReader br = new BufferedReader(new FileReader("d:\\temp\\test.java")); 4 // 没有显式使用final修饰,但只要不对该变量重新赋值,该变量就是有效的 5 final PrintStream ps = new PrintStream(new FileOutputStream("d:\\temp\\data.txt")); 6 7 // 只要将两个资源放在try后的圆括号内即可 8 try (br; ps) { 9 // 使用两个资源 10 System.out.println(br.readLine()); 11 ps.println("Java 9增强的自动资源管理"); 12 } 13 }
2. Java 7多异常捕获
多 catch 代码块虽然客观上提高了程序的健壮性,但是也导致了程序代码量大大增加。如果有些异常种类不同,但捕获之后的处理是相同的,例如以下代码。
try {
// 可能会发生异常的语句
} catch (FileNotFoundException e) {
// 调用方法methodA处理
} catch (IOException e) {
// 调用方法methodA处理
} catch (ParseException e) {
// 调用方法methodA处理
}
三个不同类型的异常,要求捕获之后的处理都是调用 methodA 方法。为了解决这种问题,Java 7 推出了多异常捕获技术,可以把这些异常合并处理。上述代码修改如下:
try {
// 可能会发生异常的语句
} catch (IOException | ParseException e) {
// 调用方法methodA处理
}
注意:由于 FileNotFoundException 属于 IOException 异常,IOException 异常可以捕获它的所有子类异常。所以不能写成 FileNotFoundException | IOException | ParseException 。
使用一个 catch 块捕获多种类型的异常时需要注意如下两个地方。
1) 捕获多种类型的异常时,多种异常类型之间用竖线|隔开。
2) 捕获多种类型的异常时,异常变量有隐式的 final 修饰,因此程序不能对异常变量重新赋值。
下面程序示范了 Java 7 提供的多异常捕获。
1 public static void main(String[] args) { 2 try { 3 int a = Integer.parseInt(args[0]); 4 int b = Integer.parseInt(args[1]); 5 int c = a / b; 6 System.out.println("您输入的两个数相除的结果是:" + c); 7 } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException e) { 8 System.out.println("程序发生了数组越界、数字格式异常、算术异常之一"); 9 // 捕获多异常时,异常变量默认有final修饰 10 // 所以下面代码有错 11 e = new ArithmeticException("test"); // Line 11 12 } catch (Exception e) { 13 System.out.println("未知异常"); 14 // 捕获一种类型的异常时,异常变量没有final修饰 15 // 所以下面代码完全正确 16 e = new RuntimeException("test"); // Line 16 17 } 18 }
上面程序中第一行粗体字代码使用了 IndexOutOfBoundsException | NumberFormatException | ArithmeticException来定义异常类型,这就表明该 catch 块可以同时捕获这 3 种类型的异常。
捕获多种类型的异常时,异常变量使用隐式的 final 修饰,因此上面程序的第 11 行代码将产生编译错误;捕获一种类型的异常时,异常变量没有 final 修饰,因此上面程序的第 16 行代码完全正确。
3. 自定义异常
如果 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 类,在该类中包含两个构造方法。
提示:因为自定义异常继承自 Exception 类,因此自定义异常类中包含父类所有的属性和方法。
4. 异常跟踪栈
异常对象的 printStackTrace() 方法用于打印异常的跟踪栈信息,根据 printStackTrace() 方法的输出结果,开发者可以找到异常的源头,并跟踪到异常一路触发的过程。
看下面用于测试 printStackTrace 的例子程序。
1 class SelfException extends RuntimeException { 2 SelfException() { 3 } 4 SelfException(String msg) { 5 super(msg); 6 } 7 } 8 public class PrintStackTraceTest { 9 public static void main(String[] args) { 10 firstMethod(); 11 } 12 public static void firstMethod() { 13 secondMethod(); 14 } 15 public static void secondMethod() { 16 thirdMethod(); 17 } 18 public static void thirdMethod() { 19 throw new SelfException("自定义异常信息"); 20 } 21 }
上面程序中 main 方法调用 firstMethod,firstMethod 调用 secondMethod,secondMethod 调用 thirdMethod,thirdMethod 直接抛出一个 SelfException 异常。
运行上面程序,会看到如下所示的结果:
Exception in thread "main" Test.SelfException: 自定义异常信息
at Test.PrintStackTraceTest.thirdMethod(PrintStackTraceTest.java:26)
at Test.PrintStackTraceTest.secondMethod(PrintStackTraceTest.java:22)
at Test.PrintStackTraceTest.firstMethod(PrintStackTraceTest.java:18)
at Test.PrintStackTraceTest.main(PrintStackTraceTest.java:14)
上面运行结果的第 2 行到第 5 行之间的内容是异常跟踪栈信息,从打印的异常信息我们可以看出,异常从 thirdMethod 方法开始触发,传到 secondMethod 方法,再传到 firstMethod 方法,最后传到 main 方法,在 main 方法终止,这个过程就是 Java 的异常跟踪栈。
实例:
1 import java.io.*; 2 import java.util.Scanner; 3 4 public class App { 5 public static void main( String[] args ) { 6 7 // Java 7自动资源管理 8 try ( 9 // 声明、初始化两个可关闭的资源 10 // try 语句会自动关闭这两个资源 11 BufferedReader br = new BufferedReader(new FileReader("d:\\temp\\test.java")); 12 PrintStream ps = new PrintStream(new FileOutputStream("d:\\temp\\data.txt"))) { 13 14 // 使用两个资源 15 System.out.println(br.readLine()); 16 ps.println("Java 7自动资源管理"); 17 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 22 // 多异常捕获 23 Scanner scanner = new Scanner(System.in); 24 int num = 0; 25 System.out.println("请输入一个整数:"); 26 try { 27 num = scanner.nextInt(); // 输入字母时有异常 28 num = num / 0; // 除零异常 29 } catch (InputMismatchException | ArithmeticException e) { 30 e.printStackTrace(); 31 } 32 33 // Test SelfException 34 firstMethod(); 35 } 36 37 public static void firstMethod() { 38 secondMethod(); 39 } 40 public static void secondMethod() { 41 thirdMethod(); 42 } 43 public static void thirdMethod() { 44 throw new SelfException("自定义异常信息"); 45 } 46 47 } 48 49 class SelfException extends RuntimeException { 50 SelfException() { 51 } 52 SelfException(String msg) { 53 super(msg); 54 } 55 }
*注: d:\\temp\\test.java 里的内容是 “package com.example;”(只有一行),需要手动创建。d:\\temp\\data.txt 是程序自动创建,目录 d:\\temp\\ 可自行调整。
输出:
package com.example;
请输入一个整数:
5
java.lang.ArithmeticException: / by zero
at com.example.ExceptionApp.main(ExceptionApp.java:55)
Exception in thread "main" com.example.SelfException: 自定义异常信息
at com.example.ExceptionApp.thirdMethod(ExceptionApp.java:74)
at com.example.ExceptionApp.secondMethod(ExceptionApp.java:71)
at com.example.ExceptionApp.firstMethod(ExceptionApp.java:68)
at com.example.ExceptionApp.main(ExceptionApp.java:61)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)