Exception Handling in Java

前言

Java对异常的处理是一个比较重要的模块。java中的异常可能来自不同类型的情况,例如用户输入的错误数据,硬件故障,网络连接故障,数据库服务器故障等。请注意,Java异常处理是一个仅用于处理运行时错误的框架,编译时错误不会由java中的异常处理来处理。

异常关键字

  1. “throw”:如果发生任何异常,Java异常框架就会创建一个异常对象,然后Java runtime开始处理它们。 有时我们可能想要在代码中显式生成异常,例如在用户认证程序中,如果密码为空,我们应该向客户端抛出异常。 throw关键字用于向运行时抛出异常来处理它。
  2. “throws ”:当我们在方法中抛出任何异常而不处理它时,我们需要在方法签名中使用throws关键字来让调用者程序知道可能由方法抛出的异常。 调用方法可能会处理这些异常,或使用throws关键字将其传播给它的调用方法。 我们可以在throws子句中提供多个异常,它也可以用于main()方法。
  3. “try-catch”:我们使用“try-catch”来对我们的异常代码进行处理,“try”是异常处理块的开始,而“catch”是结束,我们能够使用多个“catch”来捕捉异常,并且需要在“catch”中写入参数,用来表明异常的类型。
  4. “finally ”:“finally”块是可选的但是却必须与“try-catch”块一起使用,当我们执行程序的过程中,突然出现异常,但是某些资源却开启了没有关闭,这个时候“finally”就被派上了用场。“finally”块一直都会实现无论异常有没有发生。

    以下是简单的异常处理的例子:

public class ExceptionHandling {

    public static void main(String[] args) throws FileNotFoundException, IOException {
        try {
            testException(-5);
            testException(-10);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Releasing resources");
        }

        testException(15);
    }

        public static void testException (int i) throws FileNotFoundException, IOException {
            if (i < 0) {
                FileNotFoundException myException = new FileNotFoundException("Negative Integer" + i);
                throw myException;
            } else if (i > 10) {
                throw new IOException("Only supported for index 0 to 10");
            }
        }
}

上面的输出结果是:

java.io.FileNotFoundException: Negative Integer -5
    at com.test.ExceptionHandling.testException(ExceptionHandling.java:25)
    at com.test.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
    at com.test.ExceptionHandling.testException(ExceptionHandling.java:28)
    at com.test.ExceptionHandling.main(ExceptionHandling.java:20)

注意到,testException()方法使用throw关键字抛出异常,方法签名使用throws关键字让调用者知道它可能抛出异常的类型。 在main()方法中,我使用main()方法中的try-catch块处理异常,当我没有处理它时,我使用main方法中的throws子句将它抛出到运行时。 其中testException(-10)永远不会被执行,因为异常,然后执行try-catch块后执行finally块。 printStackTrace()是Exception类中有用的方法之一,用于调试目的。

  • 我们不能只有catch或finally块而没有try模块
  • 一个try要么有catch要么有finally,或者两者都可以
  • 我们不能在try-catch-fianlly之间写入任何的代码
  • 一个try模块可以有多个catch
  • try-catch可以被嵌套类似与if-else模块
  • try-catch有且只能有一个finally模块

Java异常层次结构

如前所述,当发生任何异常时,异常对象会被创建。 Java异常是分层的,继承被用来分类不同类型的异常。 Throwable是Java异常层次结构的父类,它有两个子对象 - Error 和 Exception。 异常会进一步分为checked exceptions 和 runtime exception。

  1. Errors:错误是超出应用程序范围的特殊情况,无法预测并从中恢复,例如硬件故障,JVM崩溃或内存不足错误。 这就是为什么我们有单独的错误层次结构,我们不应该尝试处理这些情况。 一些常见的错误是OutOfMemoryError和StackOverflowError。
  2. Checked Exceptions:检查异常(Checked Exceptions)是我们可以在程序中预期的特殊情况,并尝试从中恢复,例如FileNotFoundException。 我们应该捕获这个异常并向用户提供有用的消息并将其正确记录以进行调试。 Exception是所有Checked Exceptions的父类,如果我们抛出一个检查的异常,我们必须用相同的方法来捕获它,或者我们必须使用throws关键字将它声明给调用者。
  3. Runtime Exception:运行时异常是由于编程错误导致的,例如试图从Array检索元素。 在尝试检索元素之前,我们应该首先检查数组的长度,否则它可能会在运行时抛出ArrayIndexOutOfBoundException。 RuntimeException是所有运行时异常的父类。 如果我们在方法中抛出任何运行时异常,则不需要在方法签名抛出子句中指定它们。运行时异常可以被避免通过更为优秀的编程。

    这里写图片描述

Java异常处理—有用的方法

Java异常及其所有子类都不提供任何特定方法,并且所有方法都在基类Throwable中定义。 创建异常类是为了指定不同类型的异常场景,以便我们可以根据其类型轻松识别根本原因并处理异常。 Throwable类实现了可互操作性的Serializable接口。

Throwable class中的一些方法:

  1. public String getMessage():此方法返回的Throwable的消息字符串,该消息可以在创建异常的时候通过该构造方法时被提供。

  2. public String getLocalizedMessage():提供此方法以便子类可以覆盖它以向调用程序提供特定于语言环境的消息。 此方法的可执行类实现可以使用getMessage()方法来返回异常消息。

  3. public synchronized Throwable getCause() :此方法返回异常的原因或结果未知的空id。

  4. public String toString() :此方法以字符串格式返回有关Throwable的信息,返回的String包含Throwable类和本地消息。

  5. public void printStackTrace():此方法将堆栈跟踪信息打印到标准错误流,此方法可以重载,我们可以传递PrintStream或PrintWriter作为参数以将堆栈跟踪信息写入文件或流。

Java7的自动资源管理和Catch块改进

如果你在单个try块中捕获了很多异常,你会注意到catch块代码看起来非常难看,其中主要由冗余代码组成以记录错误,而Java 7的一个特性是catch块的改进 我们可以在一个catch块中捕获多个异常。
以下是Java7 新特性的简单例子:

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

在Java7 新特性中有许多限制,包括异常对象是final,并且我们不能在catch块中修改它,关于更多细节会在下个博客中写出。

大多数情况下,我们使用finally块来关闭资源,有时我们会忘记关闭它们并在资源耗尽时获取运行时异常。 这些异常很难调试,我们可能需要查看我们使用该类型资源的每个地方,以确保我们正在关闭它。 所以Java 7的一个改进是try-with-resources,我们可以在try语句中创建一个资源,并在try-catch块中使用它。 当try-catch块执行时,运行时环境会自动关闭这些资源。
带有这种改进的try-catch块的示例如下:

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

Java中的异常处理 - 创建自定义异常类

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

以下是简单例子:

public class MyException extends Exception {

    private static final long serialVersionUID = 4664456874499611218L;

    private String errorCode = "Unknown_Exception";

    public MyException (String message, String errorCode) {
        super(message);
        this.errorCode = errorCode;
    }

    public String getErrorCode () {
        return this.errorCode;
    }
}
public class CustomExceptionExample {

    public static void main(String[] args) throws MyException {
        try {
            processFile("file.txt");
        } catch (MyException e) {
            processErrorCode(e);
        }
    }

    private static void processErrorCode (MyException e) throws MyException {
        switch (e.getErrorCode()) {
            case "BAD_FILE_TYPE":
                System.out.println("Bad File Type, notify user");
                throw e;
            case "FILE_NOT_FOUND_EXCEPTION":
                System.out.println("File Not Found, notify user");
                throw e;
            case "FILE_CLOSE_EXCEPTION":
                System.out.println("File Close failed, just log it.");
                break;
            default:
                System.out.println("Unknown exception occured, lets log it for further debugging."+e.getMessage());
                e.printStackTrace();
        }
    }

    private static void processFile(String file) throws MyException {
        InputStream fis = null;
        try {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            throw new MyException(e.getMessage(),"FILE_NOT_FOUND_EXCEPTION");
        }finally{
            try {
                if(fis !=null)fis.close();
            } catch (IOException e) {
                throw new MyException(e.getMessage(),"FILE_CLOSE_EXCEPTION");
            }
        }
    }
}

请注意,我们可以有一个单独的方法来处理不同类型的错误代码,我们可以从不同的方法中获取错误代码,其中一些方法会消耗殆尽,因为我们可能不想通知用户,或者也有部分会去返回通知用户。

在这里,我扩展了Exception,以便每当产生这个异常时,它必须在方法中处理或返回给调用者程序,如果我们扩展了RuntimeException,则不需要在throws子句中指定它。 这是一个设计决策,但我总是喜欢检查异常,因为我知道在调用任何方法时可以得到的异常,并采取适当的措施来处理它们。

Java中的异常处理 - 更多实践

  1. Use Specific Exceptions:Exception层次结构的基类没有提供任何有用的信息,这就是为什么Java有这么多的异常类,比如IOException,其他子类如FileNotFoundException,EOFException等。我们应该总是抛出并捕获特定的异常类,以便调用者知道 容易发生异常的根本原因并对其进行处理。 这使调试变得简单,并帮助客户端应用程序正确处理异常。

  2. Throw Early or Fail-Fast:我们应该尽可能早地抛出异常。 考虑上面的processFile()方法,如果我们将null参数传递给此方法,我们将得到以下异常:

Exception in thread "main" java.lang.NullPointerException
    at java.io.FileInputStream.<init>(FileInputStream.java:134)
    at java.io.FileInputStream.<init>(FileInputStream.java:97)
    at com.test.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
    at com.test.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

在调试时,我们必须仔细查看堆栈跟踪以确定异常的实际位置。 如果我们改变我们的实现逻辑以尽早检查这些异常,

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

然后,异常堆栈跟踪将如下所示,清楚地显示发生异常的位置以及清除消息。

com.test.exceptions.MyException: File name can't be null
    at com.test.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
    at com.test.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
  1. Catch Late:由于java强制要么处理检查的异常,要么在方法签名中声明它,有时开发人员往往会捕获异常并记录错误。 但是这种做法是有害的,因为调用者程序没有得到异常通知。我们能够捕捉异常仅当我们能够正确的处理异常的时候。 例如,在上面的方法中,我将异常抛回到调用方法来处理它。 其他应用程序可能会用同样的方法来处理异常。 在实现任何功能时,我们应该总是将异常抛回给调用者,让他们决定如何处理它。

  2. Closing Resources :由于异常停止了程序的处理,所以我们应该关闭所有资源,或者使用Java 7的try-with-resources增强功能让Java运行时关闭它。

  3. Logging Exceptions:我们应该记录异常消息,并在抛出异常时提供清晰的消息,以便调用者知道异常发生的原因。 我们应该总是避免只使用异常的空catch块,这些空的catch块不会为调试提供任何有意义的异常细节。

  4. Single catch block for multiple exceptions:大多数情况下,我们会记录异常详细信息并向用户提供消息,在这种情况下,我们应该使用java 7功能来处理单个catch块中的多个异常。 这种方法会减少我们的代码liang,并且让代码更简洁。

  5. Using Custom Exceptions:在设计时定义异常处理策略总是更好,而不是抛出和捕获多个异常,我们可以通过error Code创建自定义异常,并且调用者程序可以处理这些错误代码。 创建一个公共方法来处理不同的错误代码并使用它也是一个好主意。

  6. Naming Conventions and Packaging:在创建自定义异常时,确保它以Exception结尾,以便从名称本身中清楚的了解到这是一个异常。 另外请确保包结构层次就像它在JDK中所做的那样,例如IOException是所有IO操作的基本异常。

  7. Use Exceptions Judiciously:异常是有代价的,有时并不需要抛出异常,我们可以返回一个布尔变量给调用者程序来明确操作是否成功。这在操作是可选的并且不希望程序由于失败而卡住的情况下很有用。 例如,在更新第三方Web服务数据库中的股票报价时,如果连接失败,我们可能希望避免抛出异常。

  8. Document the Exceptions Thrown:使用javadoc @throws来清楚地指定方法抛出的异常,这在当你提供一个接口供其他应用程序使用时十分有用。

原文:https://www.journaldev.com/1696/exception-handling-in-java

猜你喜欢

转载自blog.csdn.net/vivianXuejun/article/details/80146701
今日推荐