🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
{% raw %} # Java 中的异常 原文:http://zetcode.com/lang/java/exceptions/ 在 Java 教程的这一章中,我们将处理异常。 Java 使用异常来处理错误。 在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存数据。 当我们的应用尝试连接到站点时,互联网连接可能会断开。 用户将无效数据填充到表单。 这些错误可能会使应用崩溃,使其无法响应,并且在某些情况下甚至会损害系统的安全性。 程序员有责任处理可以预期的错误。 在 Java 中,我们可以识别三种异常:受检的异常,非受检的异常和错误。 ## Java 受检的异常 受检的异常是可以预期并从中恢复的错误情况(无效的用户输入,数据库问题,网络中断,文件缺失)。 除`RuntimeException`及其子类之外的`Exception`的所有子类都是受检的异常。 `IOException`,`SQLException`或`PrinterException`是受检的异常的示例。 Java 编译器强制将受检的异常捕获或在方法签名中声明(使用`throws`关键字)。 ## Java 非受检的异常 非受检的异常是无法预期和无法恢复的错误条件。 它们通常是编程错误,无法在运行时处理。 非受检的异常是`java.lang.RuntimeException`的子类。 `ArithmeticException`,`NullPointerException`或`BufferOverflowException`属于这组异常。 Java 编译器不强制执行非受检的异常。 ## Java 错误 错误是程序员无法解决的严重问题。 例如,应用无法处理硬件或系统故障。 错误是`java.lang.Error`类的实例。 错误的示例包括`InternalError`,`OutOfMemoryError`,`StackOverflowError`或`AssertionError`。 错误和运行时异常通常称为非受检的异常。 ## Java `try`,`catch`和`finally` `try`,`catch`和`finally`关键字用于处理异常。 `throws`关键字在方法声明中用于指定哪些异常不在方法内处理,而是传递给程序的下一个更高级别。 `throw`关键字导致抛出已声明的异常实例。 引发异常后,运行时系统将尝试查找适当的异常处理器。 调用栈是为处理器搜索的方法的层次结构。 ## Java 非受检的异常示例 Java 受检的异常包括`ArrayIndexOutOfBoundsException`,`UnsupportedOperationException`,`NullPointerException`和`InputMismatchException`。 ### `ArrayIndexOutOfBoundsException` 抛出`ArrayIndexOutOfBoundsException`表示已使用非法索引访问了数组。 索引为负或大于或等于数组的大小。 `com/zetcode/ArrayIndexOutOfBoundsEx.java` ```java package com.zetcode; public class ArrayIndexOutOfBoundsEx { public static void main(String[] args) { int[] n = { 5, 2, 4, 5, 6, 7, 2 }; System.out.format("The last element in the array is %d%n", n[n.length]); } } ``` 上面的程序中有一个错误。 我们尝试访问一个不存在的元素。 这是编程错误。 没有理由要处理此错误:必须固定代码。 ```java System.out.format("The last element in the array is %d%n", n[n.length]); ``` 数组索引从零开始。 因此,最后一个索引是`n.length - 1`。 ```java $ java ArrayIndexOutOfBoundsEx.java Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7 at com.zetcode.ArrayIndexOutOfBoundsEx.main(ArrayIndexOutOfBoundsEx.java:9) ``` 运行系统将抛出`java.lang.ArrayIndexOutOfBoundsException`。 这是非受检的异常的示例。 ### `UnsupportedOperationException` 抛出`UnsupportedOperationException`,表明不支持所请求的操作。 `com/zetcode/UnsupportedOperationEx.java` ```java package com.zetcode; import java.util.List; public class UnsupportedOperationEx { public static void main(String[] args) { var words = List.of("sky", "blue", "forest", "lake", "river"); words.add("ocean"); System.out.println(words); } } ``` 用`List.of()`工厂方法创建一个不可变列表。 不可变列表不支持`add()`方法; 因此,我们在运行示例时抛出了`UnsupportedOperationException`。 ### `NullPointerException` 当应用尝试使用具有`null`值的对象引用时,将引发`NullPointerException`。 例如,我们在`null`引用所引用的对象上调用实例方法。 `com/zetcode/NullPointerEx.java` ```java package com.zetcode; import java.util.ArrayList; import java.util.List; public class NullPointerEx { public static void main(String[] args) { List<String> words = new ArrayList<>() {{ add("sky"); add("blue"); add("cloud"); add(null); add("ocean"); }}; words.forEach(word -> { System.out.printf("The %s word has %d letters%n", word, word.length()); }); } } ``` 该示例遍历字符串列表,并确定每个字符串的长度。 在`null`值上调用`length()`会导致`NullPointerException`。 为了解决这个问题,我们可以在调用`length()`之前从检查列表中删除`null`值的所有`null`值。 ### `InputMismatchException` `Scanner`类抛出`InputMismatchException`,以指示检索到的令牌与预期类型的​​模式不匹配。 此异常是非受检的异常的示例。 编译器不强制我们处理此异常。 `com/zetcode/InputMismatchEx.java` ```java package com.zetcode; import java.util.InputMismatchException; import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; public class InputMismatchEx { public static void main(String[] args) { System.out.print("Enter an integer: "); try { Scanner sc = new Scanner(System.in); int x = sc.nextInt(); System.out.println(x); } catch (InputMismatchException e) { Logger.getLogger(InputMismatchEx.class.getName()).log(Level.SEVERE, e.getMessage(), e); } } ``` 容易出错的代码位于`try`块中。 如果引发异常,则代码跳至`catch`块。 引发的异常类必须与`catch`关键字后面的异常匹配。 ```java try { Scanner sc = new Scanner(System.in); int x = sc.nextInt(); System.out.println(x); } ``` `try`关键字定义了可能引发异常的语句块。 ```java } catch (InputMismatchException e) { Logger.getLogger(InputMismatchEx.class.getName()).log(Level.SEVERE, e.getMessage(), e); } ``` 异常在`catch`块中处理。 我们使用`Logger`类记录错误。 ## 受检的异常 Java 受检的异常包括`SQLException`,`IOException`或`ParseException`。 ### `SQLException` 使用数据库时发生`SQLException`。 `com/zetcode/MySqlVersionEx.java` ```java package com.zetcode; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.logging.Level; import java.util.logging.Logger; public class MySqlVersionEx { public static void main(String[] args) { Connection con = null; Statement st = null; ResultSet rs = null; String url = "jdbc:mysql://localhost:3306/testdb?useSsl=false"; String user = "testuser"; String password = "test623"; try { con = DriverManager.getConnection(url, user, password); st = con.createStatement(); rs = st.executeQuery("SELECT VERSION()"); if (rs.next()) { System.out.println(rs.getString(1)); } } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } finally { if (rs != null) { try { rs.close(); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } } if (st != null) { try { st.close(); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } } if (con != null) { try { con.close(); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } } } } } ``` 该示例连接到 MySQL 数据库并找出数据库系统的版本。 连接数据库很容易出错。 ```java try { con = DriverManager.getConnection(url, user, password); st = con.createStatement(); rs = st.executeQuery("SELECT VERSION()"); if (rs.next()) { System.out.println(rs.getString(1)); } } ``` 可能导致错误的代码位于`try`块中。 ```java } catch (SQLException ex) { Logger lgr = Logger.getLogger(Version.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } ``` 发生异常时,我们跳至`catch`块。 我们通过记录发生的情况来处理异常。 ```java } finally { if (rs != null) { try { rs.close(); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } } if (st != null) { try { st.close(); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } } if (con != null) { try { con.close(); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } } } ``` 无论是否接收到异常,都将执行`finally`块。 我们正在尝试关闭资源。 即使在此过程中,也可能会有异常。 因此,我们还有其他`try/catch`块。 ### `IOException` 输入/输出操作失败时,抛出`IOException`。 这可能是由于权限不足或文件名错误造成的。 `com/zetcode/IOExceptionEx.java` ```java package com.zetcode; import java.io.FileReader; import java.io.IOException; import java.nio.charset.StandardCharsets; public class IOExceptionEx { private static FileReader fr; public static void main(String[] args) { try { char[] buf = new char[1024]; fr = new FileReader("src/resources/data.txt", StandardCharsets.UTF_8); while (fr.read(buf) != -1) { System.out.println(buf); } } catch (IOException e) { e.printStackTrace(); } finally { if (fr != null) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ``` 当我们从文件中读取数据时,我们需要处理`IOException`。 当我们尝试使用`read()`读取数据并使用`close()`关闭读取器时,可能会引发异常。 ### `ParseException` 解析操作失败时,将引发`ParseException`。 `com/zetcode/ParseExceptionEx.java` ```java package com.zetcode; import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; public class ParseExceptionEx { public static void main(String[] args) { NumberFormat nf = NumberFormat.getInstance(new Locale("sk", "SK")); nf.setMaximumFractionDigits(3); try { Number num = nf.parse("150000,456"); System.out.println(num.doubleValue()); } catch (ParseException e) { e.printStackTrace(); } } } ``` 在示例中,我们将本地化的数字值解析为 Java `Number`。 我们使用`try/catch`语句处理`ParseException`。 ## Java 抛出异常 `Throwable`类是 Java 语言中所有错误和异常的超类。 Java 虚拟机仅抛出属于此类(或其子类之一)的实例的对象,或者 Java `throw`语句可以抛出该对象。 同样,在`catch`子句中,只有此类或其子类之一可以作为参数类型。 程序员可以使用`throw`关键字引发异常。 异常通常在引发异常的地方进行处理。 方法可以通过在方法定义的末尾使用`throws`关键字来摆脱处理异常的责任。 关键字后是该方法引发的所有异常的逗号分隔列表。 被抛出的异常在调用栈中传播,并寻找最接近的匹配项。 `com/zetcode/ThrowingExceptions.java` ```java package com.zetcode; import java.util.InputMismatchException; import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; public class ThrowingExceptions { public static void main(String[] args) { System.out.println("Enter your age: "); try { Scanner sc = new Scanner(System.in); short age = sc.nextShort(); if (age <= 0 || age > 130) { throw new IllegalArgumentException("Incorrect age"); } System.out.format("Your age is: %d %n", age); } catch (IllegalArgumentException | InputMismatchException e) { Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE, e.getMessage(), e); } } } ``` 在示例中,我们要求用户输入他的年龄。 我们读取该值,如果该值超出预期的人类年龄范围,则会引发异常。 ```java if (age <= 0 || age > 130) { throw new IllegalArgumentException("Incorrect age"); } ``` 年龄不能为负值,也没有年龄超过 130 岁的记录。 如果该值超出此范围,则抛出内置`IllegalArgumentException`。 抛出此异常表示方法已传递了非法或不适当的参数。 ```java } catch (IllegalArgumentException | InputMismatchException e) { Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE, e.getMessage(), e); } ``` 从 Java 7 开始,可以在一个`catch`子句中捕获多个异常。 但是,这些异常不能是彼此的子类。 例如,`IOException`和`FileNotFoundException`不能在一个`catch`语句中使用。 下面的示例将说明如何将处理异常的责任传递给其他方法。 `thermopylae.txt` ```java The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece. ``` 我们使用此文本文件。 `com/zetcode/ThrowingExceptions2.java` ```java package com.zetcode; import java.io.BufferedReader; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class ThrowingExceptions2 { public static void readFileContents(String fname) throws IOException { BufferedReader br = null; Path myPath = Paths.get(fname); try { br = Files.newBufferedReader(myPath, StandardCharsets.UTF_8); String line; while ((line = br.readLine()) != null) { System.out.println(line); } } finally { if (br != null) { br.close(); } } } public static void main(String[] args) throws IOException { String fileName = "src/main/resources/thermopylae.txt"; readFileContents(fileName); } } ``` 本示例读取文本文件的内容。 `readFileContents()`和`main()`方法都不处理潜在的`IOException`; 我们让 JVM 处理它。 ```java public static void readFileContents(String fname) throws IOException { ``` 当我们从文件读取时,会抛出`IOException`。 `readFileContents()`方法引发异常。 处理这些异常的任务委托给调用者。 ```java public static void main(String[] args) throws IOException { String fileName = "src/main/resources/thermopylae.txt"; readFileContents(fileName); } ``` `main()`方法也会抛出`IOException`。 如果有这样的异常,它将由 JVM 处理。 ## Java `try-with-resources`语句 `try-with-resources`语句是一种特殊的`try`语句。 它是 Java 7 中引入的。在括号中,我们放置了一个或多个资源。 这些资源将在语句末尾自动关闭。 我们不必手动关闭资源。 `com/zetcode/TryWithResources.java` ```java package com.zetcode; import java.io.BufferedReader; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.logging.Level; import java.util.logging.Logger; public class TryWithResources { public static void main(String[] args) { String fileName = "src/main/resources/thermopylae.txt"; Path myPath = Paths.get(fileName); try (BufferedReader br = Files.newBufferedReader(myPath, StandardCharsets.UTF_8)) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException ex) { Logger.getLogger(TryWithResources.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); } } } ``` 在示例中,我们读取文件的内容并使用`try-with-resources`语句。 ```java try (BufferedReader br = Files.newBufferedReader(myPath, StandardCharsets.UTF_8)) { ... ``` 打开的文件是必须关闭的资源。 资源放置在`try`语句的方括号之间。 无论`try`语句是正常完成还是突然完成,输入流都将关闭。 ## Java 自定义异常 自定义异常是用户定义的异常类,它们扩展了`Exception`类或`RuntimeException`类。 使用`throw`关键字可以消除自定义异常。 `com/zetcode/JavaCustomException.java` ```java package com.zetcode; class BigValueException extends Exception { public BigValueException(String message) { super(message); } } public class JavaCustomException { public static void main(String[] args) { int x = 340004; final int LIMIT = 333; try { if (x > LIMIT) { throw new BigValueException("Exceeded the maximum value"); } } catch (BigValueException e) { System.out.println(e.getMessage()); } } } ``` 我们假定存在无法处理大量数字的情况。 ```java class BigValueException extends Exception { public BigValueException(String message) { super(message); } } ``` 我们有一个`BigValueException`类。 该类派生自内置的`Exception`类。 它使用`super`关键字将错误消息传递给父类。 ```java final int LIMIT = 333; ``` 大于此常数的数字在我们的程序中被视为`big`。 ```java if (x > LIMIT) { throw new BigValueException("Exceeded the maximum value"); } ``` 如果该值大于限制,则抛出自定义异常。 我们给异常消息`"Exceeded the maximum value"`。 ```java } catch (BigValueException e) { System.out.println(e.getMessage()); } ``` 我们捕获到异常并将其消息打印到控制台。 在 Java 教程的这一部分中,我们讨论了 Java 中的异常。 {% endraw %}