Exception Handling in Java – Java中的异常处理

最后修改: 2018年 7月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll go through the basics of exception handling in Java as well as some of its gotchas.

在本教程中,我们将了解Java中异常处理的基础知识,以及其中的一些问题。

2. First Principles

2.第一原则

2.1. What Is It?

2.1.它是什么?

To better understand exceptions and exception handling, let’s make a real-life comparison.

为了更好地理解异常和异常处理,让我们做一个现实生活中的比较。

Imagine that we order a product online, but while en-route, there’s a failure in delivery. A good company can handle this problem and gracefully re-route our package so that it still arrives on time.

想象一下,我们在网上订购了一个产品,但在途中,交货时出现了故障。一个好的公司可以处理这个问题,并优雅地重新安排我们的包裹,使它仍然按时到达。

Likewise, in Java, the code can experience errors while executing our instructions. Good exception handling can handle errors and gracefully re-route the program to give the user still a positive experience.

同样地,在Java中,代码在执行我们的指令时可能会遇到错误。良好的异常处理可以处理错误,并优雅地重新安排程序,让用户仍然有一个积极的体验.

2.2. Why Use It? 

2.2.为什么使用它?

We usually write code in an idealized environment: the filesystem always contains our files, the network is healthy, and the JVM always has enough memory. Sometimes we call this the “happy path”.

我们通常在一个理想化的环境中写代码:文件系统总是包含我们的文件,网络是健康的,而JVM总是有足够的内存。有时我们把这称为 “快乐的路径”。

In production, though, filesystems can corrupt, networks break down, and JVMs run out of memory. The wellbeing of our code depends on how it deals with “unhappy paths”.

但在生产中,文件系统会损坏,网络会中断,JVM的内存会耗尽。我们的代码的好坏取决于它如何处理 “不愉快的路径”。

We must handle these conditions because they affect the flow of the application negatively and form exceptions:

我们必须处理这些情况,因为它们会对应用程序的流程产生负面影响,并形成异常

public static List<Player> getPlayers() throws IOException {
    Path path = Paths.get("players.dat");
    List<String> players = Files.readAllLines(path);

    return players.stream()
      .map(Player::new)
      .collect(Collectors.toList());
}

This code chooses not to handle the IOException, passing it up the call stack instead. In an idealized environment, the code works fine.

这段代码选择不处理IOException,而是将其传递到调用栈上。在一个理想化的环境中,这段代码运行良好。

But what might happen in production if players.dat is missing?

但如果players.dat丢失,在生产中可能会发生什么?

Exception in thread "main" java.nio.file.NoSuchFileException: players.dat <-- players.dat file doesn't exist
    at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
    at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
    // ... more stack trace
    at java.nio.file.Files.readAllLines(Unknown Source)
    at java.nio.file.Files.readAllLines(Unknown Source)
    at Exceptions.getPlayers(Exceptions.java:12) <-- Exception arises in getPlayers() method, on line 12
    at Exceptions.main(Exceptions.java:19) <-- getPlayers() is called by main(), on line 19

Without handling this exception, an otherwise healthy program may stop running altogether! We need to make sure that our code has a plan for when things go wrong.

如果不处理这个异常,一个原本健康的程序可能会完全停止运行!我们需要确保我们的代码对出错的情况有一个计划。

Also note one more benefit here to exceptions, and that is the stack trace itself. Because of this stack trace, we can often pinpoint offending code without needing to attach a debugger.

还要注意这里的异常还有一个好处,那就是堆栈跟踪本身。因为有了这个堆栈跟踪,我们往往可以在不需要附加调试器的情况下找出违规的代码。

3. Exception Hierarchy

3.异常层次

Ultimately, exceptions are just Java objects with all of them extending from Throwable:

最终,异常只是Java对象,它们都是从Throwable扩展而来。

              ---> Throwable <--- 
              |    (checked)     |
              |                  |
              |                  |
      ---> Exception           Error
      |    (checked)        (unchecked)
      |
RuntimeException
  (unchecked)

There are three main categories of exceptional conditions:

特殊情况主要有三类。

  • Checked exceptions
  • Unchecked exceptions / Runtime exceptions
  • Errors

Runtime and unchecked exceptions refer to the same thing. We can often use them interchangeably. 

运行时和未检查的异常指的是同一件事。我们通常可以将它们互换使用。

3.1. Checked Exceptions

3.1.检查的异常

Checked exceptions are exceptions that the Java compiler requires us to handle. We have to either declaratively throw the exception up the call stack, or we have to handle it ourselves. More on both of these in a moment.

被检查的异常是Java编译器要求我们处理的异常。我们要么声明性地将异常抛上调用栈,要么我们必须自己处理它。稍后会有更多关于这两者的内容。

Oracle’s documentation tells us to use checked exceptions when we can reasonably expect the caller of our method to be able to recover.

Oracle的文档告诉我们,当我们可以合理地期望我们方法的调用者能够恢复时,要使用检查过的异常。

A couple of examples of checked exceptions are IOException and ServletException.

检查异常的几个例子是IOExceptionServletException.

3.2. Unchecked Exceptions

3.2.未检查的异常情况

Unchecked exceptions are exceptions that the Java compiler does not require us to handle.

未检查的异常是Java编译器要求我们处理的异常。

Simply put, if we create an exception that extends RuntimeException, it will be unchecked; otherwise, it will be checked.

简单地说,如果我们创建一个扩展了RuntimeException的异常,它将被取消检查;否则,它将被检查。

And while this sounds convenient, Oracle’s documentation tells us that there are good reasons for both concepts, like differentiating between a situational error (checked) and a usage error (unchecked).

虽然这听起来很方便,但Oracle的文档告诉我们,这两个概念都有很好的理由,比如区分情景错误(选中)和使用错误(未选中)。

Some examples of unchecked exceptions are NullPointerException, IllegalArgumentException, and SecurityException.

未检查的异常的一些例子是NullPointerException, IllegalArgumentException, SecurityException

3.3. Errors

3.3.错误

Errors represent serious and usually irrecoverable conditions like a library incompatibility, infinite recursion, or memory leaks.

错误代表着严重的、通常是无法恢复的情况,如库不兼容、无限递归或内存泄漏。

And even though they don’t extend RuntimeException, they are also unchecked.

即使它们没有扩展RuntimeException,它们也没有被选中。

In most cases, it’d be weird for us to handle, instantiate or extend Errors. Usually, we want these to propagate all the way up.

在大多数情况下,我们处理、实例化或扩展Errors会很奇怪。通常情况下,我们希望这些东西能一直传播下去。

A couple of examples of errors are a StackOverflowError and OutOfMemoryError.

几个错误的例子是StackOverflowErrorOutOfMemoryError

4. Handling Exceptions

4.处理异常情况

In the Java API, there are plenty of places where things can go wrong, and some of these places are marked with exceptions, either in the signature or the Javadoc:

在Java API中,有很多地方会出错,其中一些地方在签名或Javadoc中都标有异常。

/**
 * @exception FileNotFoundException ...
 */
public Scanner(String fileName) throws FileNotFoundException {
   // ...
}

As stated a little bit earlier, when we call these “risky” methods, we must handle the checked exceptions, and we may handle the unchecked ones. Java gives us several ways to do this:

正如前面所说,当我们调用这些 “有风险 “的方法时,我们必须处理被检查的异常,而我们可能处理未被检查的异常。Java给了我们几种方法来做到这一点。

4.1. throws

4.1.抛出

The simplest way to “handle” an exception is to rethrow it:

“处理 “一个异常的最简单方法是重新抛出它。

public int getPlayerScore(String playerFile)
  throws FileNotFoundException {
 
    Scanner contents = new Scanner(new File(playerFile));
    return Integer.parseInt(contents.nextLine());
}

Because FileNotFoundException is a checked exception, this is the simplest way to satisfy the compiler, but it does mean that anyone that calls our method now needs to handle it too!

因为FileNotFoundException 是一个被检查的异常,这是满足编译器的最简单方法,但是它确实意味着任何调用我们方法的人现在也需要处理它!

parseInt can throw a NumberFormatException, but because it is unchecked, we aren’t required to handle it.

parseInt可以抛出一个NumberFormatException,但由于它未被选中,我们不需要处理它。

4.2. try-catch

4.2.try-catch

If we want to try and handle the exception ourselves, we can use a try-catch block. We can handle it by rethrowing our exception:

如果我们想尝试自己处理这个异常,我们可以使用一个try-catch块。我们可以通过重新抛出我们的异常来处理它。

public int getPlayerScore(String playerFile) {
    try {
        Scanner contents = new Scanner(new File(playerFile));
        return Integer.parseInt(contents.nextLine());
    } catch (FileNotFoundException noFile) {
        throw new IllegalArgumentException("File not found");
    }
}

Or by performing recovery steps:

或者通过执行恢复步骤。

public int getPlayerScore(String playerFile) {
    try {
        Scanner contents = new Scanner(new File(playerFile));
        return Integer.parseInt(contents.nextLine());
    } catch ( FileNotFoundException noFile ) {
        logger.warn("File not found, resetting score.");
        return 0;
    }
}

4.3. finally

4.3.最终

Now, there are times when we have code that needs to execute regardless of whether an exception occurs, and this is where the finally keyword comes in.

现在,有些时候我们的代码需要执行,不管是否发生异常,这就是finally关键字的作用。

In our examples so far, there ‘s been a nasty bug lurking in the shadows, which is that Java by default won’t return file handles to the operating system.

在我们迄今为止的例子中,一直有一个潜伏在暗处的讨厌的错误,那就是Java默认不会向操作系统返回文件句柄。

Certainly, whether we can read the file or not, we want to make sure that we do the appropriate cleanup!

当然,无论我们是否能读取文件,我们都要确保做适当的清理工作!

Let’s try this the “lazy” way first:

让我们先用 “懒惰 “的方式试试吧。

public int getPlayerScore(String playerFile)
  throws FileNotFoundException {
    Scanner contents = null;
    try {
        contents = new Scanner(new File(playerFile));
        return Integer.parseInt(contents.nextLine());
    } finally {
        if (contents != null) {
            contents.close();
        }
    }
}

Here, the finally block indicates what code we want Java to run regardless of what happens with trying to read the file.

在这里,finally块指出了我们希望Java运行的代码,无论试图读取文件的情况如何。

Even if a FileNotFoundException is thrown up the call stack, Java will call the contents of finally before doing that.

即使FileNotFoundException被抛上调用栈,Java也会在这之前调用finally的内容。

We can also both handle the exception and make sure that our resources get closed:

我们也可以同时处理异常,确保我们的资源得到关闭。

public int getPlayerScore(String playerFile) {
    Scanner contents;
    try {
        contents = new Scanner(new File(playerFile));
        return Integer.parseInt(contents.nextLine());
    } catch (FileNotFoundException noFile ) {
        logger.warn("File not found, resetting score.");
        return 0; 
    } finally {
        try {
            if (contents != null) {
                contents.close();
            }
        } catch (IOException io) {
            logger.error("Couldn't close the reader!", io);
        }
    }
}

Because close is also a “risky” method, we also need to catch its exception!

因为close也是一个 “有风险 “的方法,我们也需要捕捉它的异常!

This may look pretty complicated, but we need each piece to handle each potential problem that can arise correctly.

这可能看起来相当复杂,但我们需要每一块都能正确处理可能出现的每个潜在问题。

4.4. try-with-resources

4.4.try-with-resources

Fortunately, as of Java 7, we can simplify the above syntax when working with things that extend AutoCloseable:

幸运的是,从Java 7开始,我们可以在处理扩展AutoCloseable的事物时简化上述语法。

public int getPlayerScore(String playerFile) {
    try (Scanner contents = new Scanner(new File(playerFile))) {
      return Integer.parseInt(contents.nextLine());
    } catch (FileNotFoundException e ) {
      logger.warn("File not found, resetting score.");
      return 0;
    }
}

When we place references that are AutoClosable in the try declaration, then we don’t need to close the resource ourselves.

当我们在AutoClosable 声明中放置引用时,那么我们就不需要自己关闭资源。

We can still use a finally block, though, to do any other kind of cleanup we want.

不过,我们仍然可以使用finally块来做任何我们想要的其他类型的清理工作。

Check out our article dedicated to try-with-resources to learn more.

请查看我们专门介绍try-with-resources的文章以了解更多信息。

4.5. Multiple catch Blocks

4.5.多个catch

Sometimes, the code can throw more than one exception, and we can have more than one catch block handle each individually:

有时,代码会抛出不止一个异常,我们可以用一个以上的catch块来单独处理每个异常。

public int getPlayerScore(String playerFile) {
    try (Scanner contents = new Scanner(new File(playerFile))) {
        return Integer.parseInt(contents.nextLine());
    } catch (IOException e) {
        logger.warn("Player file wouldn't load!", e);
        return 0;
    } catch (NumberFormatException e) {
        logger.warn("Player file was corrupted!", e);
        return 0;
    }
}

Multiple catches give us the chance to handle each exception differently, should the need arise.

如果需要的话,多个捕获器使我们有机会以不同的方式处理每个异常。

Also note here that we didn’t catch FileNotFoundException, and that is because it extends IOException. Because we’re catching IOException, Java will consider any of its subclasses also handled.

这里还要注意,我们没有捕获FileNotFoundException,那是因为它扩展了IOException。因为我们捕捉的是IOException,所以Java会认为它的任何子类也被处理。

Let’s say, though, that we need to treat FileNotFoundException differently from the more general IOException:

假设我们需要将FileNotFoundException与更普遍的IOException区别对待。

public int getPlayerScore(String playerFile) {
    try (Scanner contents = new Scanner(new File(playerFile)) ) {
        return Integer.parseInt(contents.nextLine());
    } catch (FileNotFoundException e) {
        logger.warn("Player file not found!", e);
        return 0;
    } catch (IOException e) {
        logger.warn("Player file wouldn't load!", e);
        return 0;
    } catch (NumberFormatException e) {
        logger.warn("Player file was corrupted!", e);
        return 0;
    }
}

Java lets us handle subclass exceptions separately, remember to place them higher in the list of catches.

Java让我们单独处理子类的异常,记得把它们放在抓取列表的较高位置。

4.6. Union catch Blocks

4.6.联盟catch区块

When we know that the way we handle errors is going to be the same, though, Java 7 introduced the ability to catch multiple exceptions in the same block:

不过,当我们知道处理错误的方式将是相同的时候,Java 7引入了在同一个块中捕捉多个异常的能力。

public int getPlayerScore(String playerFile) {
    try (Scanner contents = new Scanner(new File(playerFile))) {
        return Integer.parseInt(contents.nextLine());
    } catch (IOException | NumberFormatException e) {
        logger.warn("Failed to load score!", e);
        return 0;
    }
}

5. Throwing Exceptions

5.抛出异常情况

If we don’t want to handle the exception ourselves or we want to generate our exceptions for others to handle, then we need to get familiar with the throw keyword.

如果我们不想自己处理异常,或者我们想生成我们的异常让别人处理,那么我们就需要熟悉throw关键字。

Let’s say that we have the following checked exception we’ve created ourselves:

比方说,我们有以下我们自己创建的检查过的异常。

public class TimeoutException extends Exception {
    public TimeoutException(String message) {
        super(message);
    }
}

and we have a method that could potentially take a long time to complete:

而我们的方法有可能需要很长的时间来完成。

public List<Player> loadAllPlayers(String playersFile) {
    // ... potentially long operation
}

5.1. Throwing a Checked Exception

5.1.抛出一个被检查的异常

Like returning from a method, we can throw at any point.

像从方法中返回一样,我们可以在任何时候抛出

Of course, we should throw when we are trying to indicate that something has gone wrong:

当然,当我们试图表明有什么东西出了问题时,我们应该抛出。

public List<Player> loadAllPlayers(String playersFile) throws TimeoutException {
    while ( !tooLong ) {
        // ... potentially long operation
    }
    throw new TimeoutException("This operation took too long");
}

Because TimeoutException is checked, we also must use the throws keyword in the signature so that callers of our method will know to handle it.

因为TimeoutException被检查,我们也必须在签名中使用throws关键字,这样我们方法的调用者就知道要处理它。

5.2. Throwing an Unchecked Exception

5.2.抛出未检查的异常

If we want to do something like, say, validate input, we can use an unchecked exception instead:

如果我们想做一些事情,比如说,验证输入,我们可以使用一个未检查的异常来代替。

public List<Player> loadAllPlayers(String playersFile) throws TimeoutException {
    if(!isFilenameValid(playersFile)) {
        throw new IllegalArgumentException("Filename isn't valid!");
    }
   
    // ...
}

Because IllegalArgumentException is unchecked, we don’t have to mark the method, though we are welcome to.

因为IllegalArgumentException没有被选中,所以我们不必对该方法进行标记,尽管我们很欢迎。

Some mark the method anyway as a form of documentation.

有些人无论如何都要把这种方法作为一种文件形式来标记。

5.3. Wrapping and Rethrowing

5.3.缠绕和重抛

We can also choose to rethrow an exception we’ve caught:

我们也可以选择重新抛出我们所捕获的异常。

public List<Player> loadAllPlayers(String playersFile) 
  throws IOException {
    try { 
        // ...
    } catch (IOException io) { 		
        throw io;
    }
}

Or do a wrap and rethrow:

或者做一个包裹和重新投掷。

public List<Player> loadAllPlayers(String playersFile) 
  throws PlayerLoadException {
    try { 
        // ...
    } catch (IOException io) { 		
        throw new PlayerLoadException(io);
    }
}

This can be nice for consolidating many different exceptions into one.

这可以很好地将许多不同的例外情况合并为一个。

5.4. Rethrowing Throwable or Exception

5.4.重新抛出可抛出异常

Now for a special case.

现在是一个特殊案例。

If the only possible exceptions that a given block of code could raise are unchecked exceptions, then we can catch and rethrow Throwable or Exception without adding them to our method signature:

如果一个给定的代码块可能引发的唯一异常是unchecked异常,那么我们可以捕捉并重新抛出ThrowableException,而无需将它们加入我们的方法签名中。

public List<Player> loadAllPlayers(String playersFile) {
    try {
        throw new NullPointerException();
    } catch (Throwable t) {
        throw t;
    }
}

While simple, the above code can’t throw a checked exception and because of that, even though we are rethrowing a checked exception, we don’t have to mark the signature with a throws clause.

虽然简单,但上面的代码不能抛出一个被检查的异常,正因为如此,即使我们重新抛出一个被检查的异常,我们也不需要在签名中标注throwsclause。

This is handy with proxy classes and methods. More about this can be found here.

这对代理类和方法来说很方便。关于这一点的更多信息可以在这里找到。

5.5. Inheritance

5.5.继承性

When we mark methods with a throws keyword, it impacts how subclasses can override our method.

当我们用throws关键字标记方法时,它会影响到子类如何覆盖我们的方法。

In the circumstance where our method throws a checked exception:

在我们的方法抛出一个被检查的异常的情况下。

public class Exceptions {
    public List<Player> loadAllPlayers(String playersFile) 
      throws TimeoutException {
        // ...
    }
}

A subclass can have a “less risky” signature:

一个子类可以有一个 “风险较小 “的签名。

public class FewerExceptions extends Exceptions {	
    @Override
    public List<Player> loadAllPlayers(String playersFile) {
        // overridden
    }
}

But not a “more riskier” signature:

但不是”危险 “的签名。

public class MoreExceptions extends Exceptions {		
    @Override
    public List<Player> loadAllPlayers(String playersFile) throws MyCheckedException {
        // overridden
    }
}

This is because contracts are determined at compile time by the reference type. If I create an instance of MoreExceptions and save it to Exceptions:

这是因为合约是在编译时由引用类型决定的。如果我创建一个MoreExceptions的实例,并把它保存到Exceptions

Exceptions exceptions = new MoreExceptions();
exceptions.loadAllPlayers("file");

Then the JVM will only tell me to catch the TimeoutException, which is wrong since I’ve said that MoreExceptions#loadAllPlayers throws a different exception.

那么JVM只会告诉我catch TimeoutException,这是错误的,因为我已经说过MoreExceptions#loadAllPlayers会抛出一个不同的异常。

Simply put, subclasses can throw fewer checked exceptions than their superclass, but not more.

简单地说,子类可以抛出比其超类更少的检查异常,但不能抛出更多

6. Anti-Patterns

6.反模式

6.1. Swallowing Exceptions

6.1.吞咽的例外情况

Now, there’s one other way that we could have satisfied the compiler:

现在,还有一种方法,我们可以满足编译器的要求。

public int getPlayerScore(String playerFile) {
    try {
        // ...
    } catch (Exception e) {} // <== catch and swallow
    return 0;
}

The above is called swallowing an exception. Most of the time, it would be a little mean for us to do this because it doesn’t address the issue and it keeps other code from being able to address the issue, too.

上述情况被称为吞噬异常。大多数时候,我们这样做有点卑鄙,因为它不能解决这个问题,而且它也使其他代码不能解决这个问题。

There are times when there’s a checked exception that we are confident will just never happen. In those cases, we should still at least add a comment stating that we intentionally ate the exception:

有的时候,有一个被检查的异常,我们确信它永远不会发生。在这些情况下,我们至少应该添加一个注释,说明我们故意吃了这个异常

public int getPlayerScore(String playerFile) {
    try {
        // ...
    } catch (IOException e) {
        // this will never happen
    }
}

Another way we can “swallow” an exception is to print out the exception to the error stream simply:

另一种我们可以 “吞噬 “异常的方法是简单地将异常打印到错误流中。

public int getPlayerScore(String playerFile) {
    try {
        // ...
    } catch (Exception e) {
        e.printStackTrace();
    }
    return 0;
}

We’ve improved our situation a bit by a least writing the error out somewhere for later diagnosis.

我们已经改善了我们的情况,至少把错误写在某个地方供以后诊断。

It’d be better, though, for us to use a logger:

不过,对我们来说,使用记录仪会更好。

public int getPlayerScore(String playerFile) {
    try {
        // ...
    } catch (IOException e) {
        logger.error("Couldn't load the score", e);
        return 0;
    }
}

While it’s very convenient for us to handle exceptions in this way, we need to make sure that we aren’t swallowing important information that callers of our code could use to remedy the problem.

虽然我们以这种方式处理异常是非常方便的,但我们需要确保我们没有吞噬重要的信息,而这些信息是我们代码的调用者可以用来补救问题的。

Finally, we can inadvertently swallow an exception by not including it as a cause when we are throwing a new exception:

最后,当我们抛出一个新的异常时,如果不把它作为一个原因,我们就会无意中吞掉一个异常。

public int getPlayerScore(String playerFile) {
    try {
        // ...
    } catch (IOException e) {
        throw new PlayerScoreException();
    }
}

Here, we pat ourselves on the back for alerting our caller to an error, but we fail to include the IOException as the cause. Because of this, we’ve lost important information that callers or operators could use to diagnose the problem.

在这里,我们为提醒调用者注意错误而拍手称快,但我们未能将IOException作为原因。因为这样,我们失去了调用者或操作者可以用来诊断问题的重要信息。

We’d be better off doing:

我们最好是这样做。

public int getPlayerScore(String playerFile) {
    try {
        // ...
    } catch (IOException e) {
        throw new PlayerScoreException(e);
    }
}

Notice the subtle difference of including IOException as the cause of PlayerScoreException.

注意包括IOException作为PlayerScoreExceptioncause的微妙差别。

6.2. Using return in a finally Block

6.2.在finallyBlock中使用return

Another way to swallow exceptions is to return from the finally block. This is bad because, by returning abruptly, the JVM will drop the exception, even if it was thrown from by our code:

另一种吞噬异常的方法是在finally块中return。这是不好的,因为通过突然的返回,JVM会放弃这个异常,即使它是由我们的代码抛出的。

public int getPlayerScore(String playerFile) {
    int score = 0;
    try {
        throw new IOException();
    } finally {
        return score; // <== the IOException is dropped
    }
}

According to the Java Language Specification:

根据Java语言规范的规定。

If execution of the try block completes abruptly for any other reason R, then the finally block is executed, and then there is a choice.

如果try块的执行由于任何其他原因突然完成R,那么finally块将被执行,然后有一个选择。

If the finally block completes normally, then the try statement completes abruptly for reason R.

如果finally块正常完成,那么try语句就会因为R原因而突然完成。

If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).

如果finally块由于原因S突然完成,那么try语句由于原因S突然完成(原因R被丢弃)。

6.3. Using throw in a finally Block

6.3.在finally Block中使用throw

Similar to using return in a finally block, the exception thrown in a finally block will take precedence over the exception that arises in the catch block.

与在finally块中使用return类似,在finally块中抛出的异常将优先于catch块中产生的异常。

This will “erase” the original exception from the try block, and we lose all of that valuable information:

这将从try块中 “擦除 “原来的异常,我们将失去所有有价值的信息。

public int getPlayerScore(String playerFile) {
    try {
        // ...
    } catch ( IOException io ) {
        throw new IllegalStateException(io); // <== eaten by the finally
    } finally {
        throw new OtherException();
    }
}

6.4. Using throw as a goto

6.4.使用throw作为goto

Some people also gave into the temptation of using throw as a goto statement:

有些人还屈从于使用throw作为goto语句的诱惑。

public void doSomething() {
    try {
        // bunch of code
        throw new MyException();
        // second bunch of code
    } catch (MyException e) {
        // third bunch of code
    }		
}

This is odd because the code is attempting to use exceptions for flow control as opposed to error handling.

这很奇怪,因为代码正试图使用异常来进行流程控制,而不是错误处理。

7. Common Exceptions and Errors

7.常见的异常情况和错误

Here are some common exceptions and errors that we all run into from time to time:

这里有一些常见的例外情况和错误,我们都会时常遇到。

7.1. Checked Exceptions

7.1.被检查的异常情况

  • IOException – This exception is typically a way to say that something on the network, filesystem, or database failed.

7.2. RuntimeExceptions

7.2.RuntimeExceptions

  • ArrayIndexOutOfBoundsException – this exception means that we tried to access a non-existent array index, like when trying to get index 5 from an array of length 3.
  • ClassCastException – this exception means that we tried to perform an illegal cast, like trying to convert a String into a List. We can usually avoid it by performing defensive instanceof checks before casting.
  • IllegalArgumentException – this exception is a generic way for us to say that one of the provided method or constructor parameters is invalid.
  • IllegalStateException – This exception is a generic way for us to say that our internal state, like the state of our object, is invalid.
  • NullPointerException – This exception means we tried to reference a null object. We can usually avoid it by either performing defensive null checks or by using Optional.
  • NumberFormatException – This exception means that we tried to convert a String into a number, but the string contained illegal characters, like trying to convert “5f3” into a number.

7.3. Errors

7.3.错误

  • StackOverflowError – this exception means that the stack trace is too big. This can sometimes happen in massive applications; however, it usually means that we have some infinite recursion happening in our code.
  • NoClassDefFoundError – this exception means that a class failed to load either due to not being on the classpath or due to failure in static initialization.
  • OutOfMemoryError –  this exception means that the JVM doesn’t have any more memory available to allocate for more objects. Sometimes, this is due to a memory leak.

8. Conclusion

8.结论

In this article, we’ve gone through the basics of exception handling as well as some good and poor practice examples.

在这篇文章中,我们介绍了异常处理的基础知识,以及一些好的和不好的实践案例。

As always, all code found in this article can be found over on GitHub!

一如既往,本文中的所有代码都可以在GitHub上找到