Java 中的异常处理

简介

AN_EXCEPTION_是在程序执行期间可能发生的错误事件,它会中断程序的正常流程。Java提供了一种健壮的、面向对象的方法来处理异常场景,称为Java异常处理。

Java中的异常可能是由不同类型的情况引起的,例如用户输入的数据错误、硬件故障、网络连接故障或数据库服务器关闭。指定在特定异常情况下要执行的操作的代码称为异常处理。

抛出和捕获异常

当执行语句时发生错误时,Java会创建一个异常对象。异常对象包含大量调试信息,如方法层次结构、发生异常的行号和异常类型。

如果方法中发生异常,则创建异常对象并将其移交给运行时环境的过程称为_)](/community/tutorials/difference-jdk-vs-jre-vs-jvm)试图找到异常的处理程序。异常处理程序是可以处理异常对象的代码块。

  • 查找异常处理程序的逻辑从搜索发生错误的方法开始。
  • 如果没有找到合适的处理程序,那么它将移动到调用方方法。

因此,如果方法的调用堆栈是A->B->C,并且在方法C中引发异常,则搜索合适的处理程序将从C->B->a移动。

如果找到适当的异常处理程序,则将异常对象传递给处理程序进行处理。处理程序被称为_捕捉异常_。如果找不到合适的异常处理程序,则程序终止并将有关异常的信息打印到控制台。

Java异常处理框架仅用于处理运行时错误。编译时错误必须由编写代码的开发人员修复,否则程序将无法执行。

Java异常处理关键字

Java提供了用于异常处理目的的特定关键字。

1.抛出 -我们知道如果发生错误,将创建一个异常对象,然后Java运行时开始处理它们。有时,我们可能希望在代码中显式生成异常。例如,在用户身份验证程序中,如果密码为null,则应该向客户端抛出异常。关键字throw用于向运行时抛出异常来处理它。 2.抛出异常 -当我们在方法中抛出异常而不处理它时,我们必须在方法签名中使用throws关键字来让调用方程序知道该方法可能抛出的异常。调用方方法可能会处理这些异常,或者使用关键字throws将它们传播到其调用方方法。我们可以在throws子句中提供多个异常,也可以与main()方法一起使用。 3.try-Catch -我们在代码中使用try-catch块进行异常处理。try是块的开始,catchtry块的末尾,用于处理异常。我们可以有多个带有try块的catch块。try-catch块也可以嵌套。catch块需要类型为Exception的参数。 4.Finally -finally块是可选的,只能和try-catch块一起使用。因为异常会暂停执行过程,所以我们可能打开了一些不会关闭的资源,所以我们可以使用finally块。无论是否发生异常,finally块总是被执行。

异常处理示例

 1[label ExceptionHandling.java]
 2package com.journaldev.exceptions;
 3
 4import java.io.FileNotFoundException;
 5import java.io.IOException;
 6
 7public class ExceptionHandling {
 8
 9    public static void main(String[] args) throws FileNotFoundException, IOException {
10    	try {
11    		testException(-5);
12    		testException(-10);
13    	} catch(FileNotFoundException e) {
14    		e.printStackTrace();
15    	} catch(IOException e) {
16    		e.printStackTrace();
17    	} finally {
18    		System.out.println("Releasing resources");
19    	}
20    	testException(15);
21    }
22
23    public static void testException(int i) throws FileNotFoundException, IOException {
24    	if (i < 0) {
25    		FileNotFoundException myException = new FileNotFoundException("Negative Integer " + i);
26    		throw myException;
27    	} else if (i > 10) {
28    		throw new IOException("Only supported for index 0 to 10");
29    	}
30    }
31}
  • testException()方法使用throw关键字抛出异常。方法签名使用throws关键字来让调用方知道它可能抛出的异常类型。
  • main()方法中,我使用main()方法中的try-catch块处理异常。当我不处理它时,我使用main()方法中的throws子句将它传播到运行时。
  • 由于异常,testException(-10)永远不会执行,然后执行finally块。

printStackTrace()Exception类中用于调试的有用方法之一。

此代码将输出以下内容:

1[secondary_label Output]
2java.io.FileNotFoundException: Negative Integer -5
3    at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
4    at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
5Releasing resources
6Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
7    at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
8    at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)

需要注意的一些要点:

  • 如果没有try语句,就不能有catch或finally子句。
  • 一个try语句应该有catch块或者finally块,它可以有两个块。
  • 我们不能在try-catch-finally块之间编写任何代码。
  • 我们可以用一个try语句来实现多个catch块。
  • try-catch块可以嵌套,类似于if-else语句。
  • 我们只能有一个带有try-catch语句的finally块。

Java异常层次结构

如前所述,当引发异常时,将创建一个异常对象。Java异常是分层的,继承用于对不同类型的异常进行分类。Throwable是Java异常层次结构的父类,它有两个子对象--ErrorException异常又分为选中的异常和运行时的`异常。

1.错误错误是超出应用范围的异常情况,无法预期和恢复。例如,硬件故障、Java虚拟机(JVM)崩溃或内存不足错误。这就是为什么我们有一个单独的‘错误’等级,我们不应该试图处理这些情况。常见的Error‘有OutOfMory yErrorStackOverflow Error。 2.检查异常 :检查异常是指我们可以在程序中预料到并尝试从中恢复的异常情况。例如,FileNotFoundException。我们应该捕获此异常,并向用户提供有用的消息,并将其正确记录以用于调试目的。Exception是所有选中的Exceptions的父类。如果我们抛出一个选中的Exception,我们必须在同一方法中Catch它,或者我们必须使用throws关键字将它传播给调用者。 3.运行时异常 :运行时异常‘是由编程错误引起的。例如,尝试从数组中检索元素。在尝试检索元素之前,我们应该首先检查数组的长度,否则它可能会在运行时抛出ArrayIndexOutOfExceptionRounmeException是所有RuntimeException的父类。如果我们在一个方法中出任何RuntimeException,则不需要在方法签名throws`子句中指定它们。通过更好的编程,可以避免运行时异常。

Java异常层次结构图。可抛出的在图表的顶部。此诊断树的一个分支是错误。下面的错误是OutOfMemory错误和ioError。这棵树的另一个分支是Except。异常分为IOException和RounmeException。IOException下面是FileNotFoundException。运行异常下面是NullPointerException.

一些有用的异常类方法

JavaException及其所有子类没有提供任何具体的方法,所有的方法都在基类Throwable中定义。创建Exception类是为了指定不同类型的Exception场景,以便我们可以轻松地找出根本原因,并根据Exception的类型进行处理。Throwable类实现了Serializable接口以实现互操作性。

Throwable类的一些有用的方法是:

公共字符串getMessage() -该方法返回ThrowableString消息,该消息可以在通过构造函数创建异常时提供。 2.公共字符串getLocalizedMessage() -提供此方法,以便子类可以覆盖它,以向调用程序提供特定于地区的消息。该方法的Throwable类实现使用getMessage()方法返回异常消息。 3.公共同步的Throwable getCause() -该方法返回异常的原因,如果原因不明则返回null。 4.公共字符串toString() -该方法返回String格式的Throwable信息,返回的String包含Throwable类的名称和本地化消息。 5.PUBLIC VOID printStackTrace() -该方法将堆栈跟踪信息打印到标准错误流,该方法是重载的,我们可以传入PrintStreamPrintWriter作为参数,将堆栈跟踪信息写入文件或流。

Java 7自动资源管理和CATCH块改进

如果您在一个try‘块中捕获’到许多异常,您会注意到catch块代码主要由记录错误的冗余代码组成。在Java 7中,其中一个特性是改进的catch块,在该块中,我们可以在单个catch块中捕获多个异常。以下是具有此功能的catch块的示例:

1catch (IOException | SQLException ex) {
2    logger.error(ex);
3    throw new MyException(ex.getMessage());
4}

有一些限制,例如异常对象是final的,我们不能在catch块中修改它,请阅读[Java 7 Catch Block Improvements](/community/tutorials/java-catch-multiple-exceptions-retrow-exception)的完整分析。

大多数情况下,我们使用finally块只是为了关闭资源。有时,我们忘记关闭它们,并在资源耗尽时获取运行时异常。这些异常很难调试,我们可能需要检查我们正在使用该资源的每个位置,以确保我们正在关闭它。在Java 7中,其中一个改进是try-with-resource‘,我们可以在try语句本身中创建一个资源,并在try-catch块中使用它。当执行从try-catch块出来时,运行时环境会自动关闭这些资源。以下是具有此改进的try-catch`块的示例:

1try (MyResource mr = new MyResource()) {
2    System.out.println("MyResource created in try-with-resources");
3} catch (Exception e) {
4    e.printStackTrace();
5}

自定义异常类示例

Java提供了许多可供我们使用的异常类,但有时我们可能需要创建自己的自定义异常类。例如,用适当的消息通知调用方特定类型的异常。我们可以使用自定义字段进行跟踪,例如错误代码。例如,假设我们编写了一个只处理文本文件的方法,这样当其他类型的文件作为输入被发送时,我们就可以向调用者提供适当的错误代码。

首先,创建MyException

 1[label MyException.java]
 2package com.journaldev.exceptions;
 3
 4public class MyException extends Exception {
 5
 6    private static final long serialVersionUID = 4664456874499611218L;
 7
 8    private String errorCode = "Unknown_Exception";
 9
10    public MyException(String message, String errorCode) {
11    	super(message);
12    	this.errorCode=errorCode;
13    }
14
15    public String getErrorCode() {
16    	return this.errorCode;
17    }
18}

然后创建一个CustomExceptionExample

 1[label CustomExceptionExample.java]
 2package com.journaldev.exceptions;
 3
 4import java.io.FileInputStream;
 5import java.io.FileNotFoundException;
 6import java.io.IOException;
 7import java.io.InputStream;
 8
 9public class CustomExceptionExample {
10
11    public static void main(String[] args) throws MyException {
12    	try {
13    		processFile("file.txt");
14    	} catch (MyException e) {
15    		processErrorCodes(e);
16    	}
17    }
18
19    private static void processErrorCodes(MyException e) throws MyException {
20    	switch (e.getErrorCode()) {
21    		case "BAD_FILE_TYPE":
22    			System.out.println("Bad File Type, notify user");
23    			throw e;
24    		case "FILE_NOT_FOUND_EXCEPTION":
25    			System.out.println("File Not Found, notify user");
26    			throw e;
27    		case "FILE_CLOSE_EXCEPTION":
28    			System.out.println("File Close failed, just log it.");
29    			break;
30    		default:
31    			System.out.println("Unknown exception occured, lets log it for further debugging." + e.getMessage());
32    			e.printStackTrace();
33    	}
34    }
35
36    private static void processFile(String file) throws MyException {
37    	InputStream fis = null;
38
39    	try {
40    		fis = new FileInputStream(file);
41    	} catch (FileNotFoundException e) {
42    		throw new MyException(e.getMessage(), "FILE_NOT_FOUND_EXCEPTION");
43    	} finally {
44    		try {
45    			if (fis != null) fis.close();
46    		} catch (IOException e) {
47    			throw new MyException(e.getMessage(), "FILE_CLOSE_EXCEPTION");
48    		}
49    	}
50    }
51}

我们可以使用单独的方法来处理从不同方法获得的不同类型的错误代码。其中一些会被使用,因为我们可能不想通知用户这一点,或者有些会被抛回以通知用户问题。

在这里,我扩展了Exception,以便每当产生此异常时,它都必须在方法中处理或返回给调用者程序。如果我们扩展RounmeException,则不需要在throws子句中指定它。

这是一个设计决定。使用选中的`Exceptions的好处是帮助开发人员了解您可以预期哪些异常,并采取适当的操作来处理它们。

Java异常处理最佳实践

*Use Specific Exception -Exception层次结构的基类不提供任何有用的信息,这就是为什么Java有这么多的异常类,比如IOException,后面还有FileNotFoundExceptionEOFException等子类。我们应该总是throwcatch特定的异常类,这样调用者就可以很容易地知道异常的根本原因并处理它们。这使得调试更容易,并帮助客户端应用程序适当地处理异常。 *Throw Early或Fail Fast -我们应该尽可能早地)方法,如果我们将null`参数传递给这个方法,我们将得到以下异常:

1[secondary_label Output]
2Exception in thread "main" java.lang.NullPointerException
3    at java.io.FileInputStream.<init>(FileInputStream.java:134)
4    at java.io.FileInputStream.<init>(FileInputStream.java:97)
5    at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
6    at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

在调试时,我们必须仔细查看堆栈跟踪,以确定异常的实际位置。如果我们更改我们的实现逻辑以提前检查这些异常,如下所示:

1private static void processFile(String file) throws MyException {
2    if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");
3
4    // ... further processing
5}

则异常堆栈跟踪将通过清除消息来指示异常发生的位置:

1[secondary_label Output]
2com.journaldev.exceptions.MyException: File name can't be null
3    at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
4    at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
  • 捕获延迟 -由于Java强制要么处理检查的异常,要么在方法签名中声明它,所以有时开发人员倾向于捕获‘异常并记录错误。但这种做法是有害的,因为调用程序不会收到任何异常通知。只有当我们能够适当地处理它们时,我们才应该‘捕捉’它们。例如,在上面的方法中,我将异常抛回调用者方法来处理它。可能希望以不同方式处理异常的其他应用程序可以使用相同的方法。在实现任何功能时,我们都应该将异常抛回‘给调用者,让他们决定如何处理。
  • 关闭资源 -由于异常导致程序停止处理,我们应该关闭Finally BLOCK中的所有资源,或者使用Java 7`Try-With-Resources‘增强让Java Runtime为您关闭。
  • 记录异常 -我们应该始终记录异常消息,并在异常时提供明确的消息,以便调用者可以轻松地知道异常发生的原因。我们应该始终避免空的catch块,它只使用异常,而不提供异常的任何有意义的细节以供调试。
  • 多个异常的单个Catch块 -大多数情况下,我们会记录异常详细信息,并向用户提供消息,在这种情况下,我们应该使用Java 7功能在单个Catch块中处理多个异常。这种方法将减少我们的代码大小,而且看起来也会更干净。
  • 使用自定义异常 -在设计时定义异常处理策略总是更好,我们可以创建一个带有错误码的自定义异常,调用方程序可以处理这些错误码,而不是抛出‘和捕捉’多个异常。创建一个实用方法来处理不同的错误代码并使用它们也是一个好主意。
  • 命名约定和打包 -当您创建自定义异常时,请确保它以Exception结尾,这样从名称本身就可以清楚地看到它是一个异常类。此外,请确保像在Java开发工具包(JDK)中那样对它们进行打包。例如,IOException是所有IO操作的基本异常。
  • 明智地使用异常 -异常代价很高,有时根本不需要抛出异常,我们可以向调用方程序返回一个布尔变量来指示操作是否成功。如果操作是可选的,并且您不希望您的程序因为失败而停滞,这会很有帮助。例如,在从第三方Web服务更新数据库中的股票报价时,我们可能希望避免在连接失败时引发异常。
  • 记录抛出的异常 -使用Javadoc@throws明确指定该方法抛出的异常。当您提供接口以供其他应用程序使用时,它非常有用。

结论

在本文中,您学习了Java中的异常处理。你学过)、catchfinally块。

Published At
Categories with 技术
Tagged with
comments powered by Disqus