throwable_您想知道的所有关于Throwable的信息

throwable

throwable

本文是有关异常的教程。 但不是通常的一种。 其中有许多内容可以告诉您异常的含义,如何抛出异常,捕获异常,已检查异常与运行时异常之间的区别,等等。 没有必要了。 这对您来说也很无聊。 如果没有,那么请阅读其中的一本,并在您了解他们所教的内容后再回来。 本文从这些教程的结尾处开始。 我们将更深入地研究Java异常,可以使用它们做什么,应该使用它们做什么以及它们可能没有听说的功能。 如果setStackTrace()getCause()getSuppressed()是您早餐时使用的方法,则可以跳过本文。 但是,如果不是这样,并且您想对此有所了解,请继续。 这篇文章很长。 写作花了很长时间,而阅读花了很长时间。 这是必需的。

介绍

在本文中,我们将讨论异常以及Java异常可以做什么以及应该做什么。 最简单的情况是抛出一个然后捕获它,但是存在更复杂的情况,例如设置原因或抑制异常。 我们将探讨这些可能性,以及更多其他可能性。 为了发现可能性,我们将开发一个简单的应用程序,并逐步创建四个版本,进一步开发该应用程序,并使用越来越多的异常处理可能性。 源代码在存储库中可用:

https://github.com/verhas/BLOG/tree/master/exception_no_stack

不同的版本在不同的Java软件包中。 有些在不同版本中未更改的类要高一包,并且没有版本化。

  • 第一个版本v1只是引发en异常,而应用程序不处理它。 测试代码期望测试设置抛出异常。 此版本是说明为什么我们需要更复杂的解决方案的基准。 我们将体验到,没有足够的信息来了解实际问题发生在哪里的异常。
  • 第二个版本v2在更高级别上捕获了该异常,并引发了一个新异常,其中包含有关异常情况的更多信息,并且新异常中嵌入了原始异常作为原因。 这种方法可以提供足够的信息来跟踪问题的位置,但是甚至可以对其进行增强,以便于阅读和识别实际问题。
  • v3将演示我们如何修改新异常的创建,以便更高级别的异常的堆栈跟踪不会指向捕获原始异常的位置,而是指向引发原始异常的位置。
  • 最后,第四版v4将演示在异常情况下即使可能无法成功完成操作也可以继续处理时如何抑制表达式。 这种“更进一步”使得最后可能有一个异常,该异常收集有关所有发现的异常情况的信息,而不仅仅是第一次出现的信息。

如果您查看代码,还将在此找到本文的原始文本,以及有助于维护代码段的设置,这些代码段将其从源代码复制到文章中,从而使所有代码段都保持最新。 对我们有用的工具是Java :: Geci。

样品申请

我们使用异常来处理超出程序正常流程的范围。 当引发异常时,程序的正常流程将中断,并且执行将停止将异常转储到某些输出。 也可以使用语言中内置的try and catch命令对来捕获这些异常。

 try {

        ... some code ...

        ... even calling methods

                      several level deep    ...

        ...   where exception may be thrown ...

      } catch (SomeException e){

        ... code having access to the exception object 'e'

            and doing someting with it (handling) ....

      }

异常本身是Java中的对象,并且可以包含很多信息。 当我们在代码中捕获异常时,我们可以访问异常对象,并且代码可以在特殊情况下也可以访问异常对象所携带的参数,从而采取行动。 可以实现我们自己的扩展Java的异常java.lang.Throwable类或直接或传递扩展Throwable某些类。 (通常,我们扩展Exception类。)我们自己的实现可以包含许多描述异常情况性质的参数。 为此,我们使用对象字段。

尽管异常可以承载的数据没有限制,但是异常所包含的信息通常不超过消息和堆栈跟踪。 在Throwable类中定义了其他参数的空间,例如导致当前参数的异常( getCause() )或一系列抑制异常( getSuppressed() )。 很少使用它们,可能是因为开发人员不了解这些功能,并且因为大多数情况很简单,不需要这些可能性。 我们将在本文中介绍这些可能性,以使您不属于无知的开发人员,这些开发人员不仅仅因为他们不了解这些方法而使用这些方法。

我们有一个示例应用程序。 它不仅仅是在catch分支中引发,捕获和处理异常以使代码继续运行,还不止于此。 这很简单,并且在您第一次学习Java编程时已阅读的教程中对此进行了解释。

我们的示例应用程序将更加复杂。 我们将在目录中列出文件,读取行,并计算wtf字符串的数量。 这样,我们就可以自动执行代码审查过程质量度量(开玩笑)。 据说,代码质量与代码审查期间的WTF数量成反比。

解决方案包含

  • 可以列出文件的FileLister
  • 可以读取文件的FileReader
  • 一个LineWtfCounter ,它将在一行中计算wtf
  • FileWtfCounter ,它将使用上一个类对整个文件中列出行的所有wtf进行计数,最后,
  • 一个ProjectWtfCounter ,它使用文件级计数器对整个项目中的wtf进行计数,列出所有文件。

版本1,投掷

该应用程序的功能非常简单,并且因为我们专注于异常处理,所以实现也很简单。 例如,文件列表类很简单,如下所示:

 package javax0.blog.demo.throwable;
 import java.util.List;
 public class FileLister { 
    public FileLister() {

    }

    public List<String> list() {

        return List.of( "a.txt" , "b.txt" , "c.txt" );

    }
 }

文件系统中有三个文件a.txtb.txtc.txt 。 当然,这是一个模拟,但是在这种情况下,我们不需要更复杂的方法来演示异常处理。 同样, FileReader也是一种模拟实现,仅用于演示目的:

 package javax0.blog.demo.throwable.v1;
 import java.util.List;
 public class FileReader {

    final String fileName; 
    public FileReader(String fileName) {

        this .fileName = fileName;

    }

    public List<String> list() {

        if (fileName.equals( "a.txt" )) {

            return List.of( "wtf wtf" , "wtf something" , "nothing" );

        }

        if (fileName.equals( "b.txt" )) {

            return List.of( "wtf wtf wtf" , "wtf something wtf" , "nothing wtf" );

        }

        if (fileName.equals( "c.txt" )) {

            return List.of( "wtf wtf wtf" , "wtf something wtf" , "nothing wtf" , "" );

        }

        throw new RuntimeException( "File is not found: " + fileName);

    }
 }

计算一行中wtf出现次数的计数器是

 package javax0.blog.demo.throwable.v1;
 public class LineWtfCounter {

    private final String line; 
    public LineWtfCounter(String line) {

        this .line = line;

    }

    public static final String WTF = "wtf" ;

    public static final int WTF_LEN = WTF.length(); 
    public int count() {

        if (line.length() == 0 ) {

            throw new LineEmpty();

        }

        // the actual lines are removed from the documentation snippet

    }
 }

为了节省空间并专注于我们的主题,代码段不显示实际的逻辑(由Java :: Geci自动删除)。 读者可以创建一个代码,该代码实际计算字符串中wtf子字符串的数量,或者简单地计算“ wtf”。 即使读者不能编写这样的代码,也可以从本文开头提到的存储库中获得。


我们应用程序中的逻辑说,如果文件中的一行的长度为零,这是一种例外情况。 在这种情况下,我们抛出异常。


通常,这种情况并不能证明是一个例外,我承认这是一个虚构的示例,但是我们需要一些简单的方法。 如果行的长度为零,则抛出LineEmpty异常。 (我们没有列出LineEmpty异常的代码。它在代码存储库中,它很简单,没什么特别的。它扩展了RuntimeException ,无需声明我们将其放置在何处。)如果您查看FileReader的模拟实现,则您会看到我们在文件c.txt中插入了空行。

使用行级计数器的文件级计数器如下:

 package javax0.blog.demo.throwable.v1;
 public class FileWtfCounter {

    // fileReader injection is omitted for brevity

    public int count() {

        final var lines = fileReader.list();

        int sum = 0 ;

        for ( final var line : lines) {

            sum += new LineWtfCounter(line).count();

        }

        return sum;

    }
 }

(同样,从打印输出中跳过了一些琐碎的行。)

这是该应用程序的第一个版本。 它没有任何特殊的异常处理。 它只是对行计数器返回的值求和,如果较低级别有异常,则行wtf计数器会自动向上传播。 在此级别上,我们不会以任何方式处理该异常。

项目级计数器非常相似。 它使用文件计数器并对结果求和。

 package javax0.blog.demo.throwable.v1;
 import javax0.blog.demo.throwable.FileLister;
 public class ProjectWftCounter {

    // fileLister injection is omitted for brevity

    public int count() {

        final var fileNames = fileLister.list();

        int sum = 0 ;

        for ( final var fileName : fileNames) {

            sum += new FileWtfCounter( new FileReader(fileName)).count();

        }

        return sum;

    }
 }

我们使用简单的测试代码对其进行测试:

 package javax0.blog.demo.throwable.v1;
 import javax0.blog.demo.throwable.FileLister;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;

public class TestWtfCounter { 
    @Test

    @DisplayName ( "Throws up for a zero length line" )

    void testThrowing() {

        Throwable thrown = catchThrowable(() ->

                new ProjectWftCounter( new FileLister())

                        .count());

        assertThat(thrown).isInstanceOf(LineEmpty. class );

        thrown.printStackTrace();

    }
 }

单元测试通常不应具有堆栈跟踪打印。 在这种情况下,我们可以演示所抛出的内容。 错误中的堆栈跟踪将向我们显示以下错误:

 javax0.blog.demo.throwable.v1.LineEmpty: There is a zero length line

    at javax0.blog.demo.throwable.v1.LineWtfCounter.count(LineWtfCounter.java:18)

    at javax0.blog.demo.throwable.v1.FileWtfCounter.count(FileWtfCounter.java:19)

    at javax0.blog.demo.throwable.v1.ProjectWftCounter.count(ProjectWftCounter.java:22)

    at javax0.blog.demo.throwable.v1.TestWtfCounter.lambda$testThrowing$0(TestWtfCounter.java:18)

    at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:62)

    ...

    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

这个异常有点问题。 当我们使用此代码时,它不会告诉我们有关有问题的实际文件和行的任何信息。 如果有一个空文件,我们必须检查所有文件和所有行。 为此编写一个应用程序并不是太困难,但是我们不想代替创建该应用程序的程序员来工作。 如果有例外,我们希望该例外能够为我们提供足够的信息,以成功解决该问题。 该应用程序必须告诉我哪个文件和哪一行有问题。

版本2,设置原因

为了在异常中提供信息,我们必须收集它并将其插入异常中。 这是我们在第二版应用程序中所做的。

第一个版本中的异常不包含文件名或行号,因为代码未将其放在此处。 该代码有这样做的充分理由。 引发异常的位置的代码没有信息,因此无法将其没有的信息插入异常。

一种有利可图的方法是将该信息与其他参数一起传递,以便在发生异常时代码可以将此信息插入到异常中。 我不推荐这种方法。 如果您查看我在GitHub上发布的源代码,则可能会找到这种做法的示例。 我不为他们感到骄傲,对不起。通常,我建议异常处理不应干扰应用程序的主数据流。 必须将其分开,因为这是一个单独的问题。

解决方案是在多个级别上处理异常,在每个级别上添加实际可用的信息。 为此,我们修改了FileWtfCounterProjectWftCounter类。

ProjectWftCounter的代码如下:

 package javax0.blog.demo.throwable.v2;
 public class FileWtfCounter {

    // some lines deleted ...

    public int count() {

        final var lines = fileReader.list();

        int sum = 0 ;

        int lineNr = 1 ;

        for ( final var line : lines) {

            try {

                sum += new LineWtfCounter(line).count();

            } catch (LineEmpty le){

                throw new NumberedLineEmpty(lineNr,le);

            }

            lineNr ++;

        }

        return sum;

    }
 }

该代码捕获了向空行发出信号并抛出新的异常的信号,该异常已经有一个参数:该行的序列号。

此异常的代码LineEmpty那样琐碎,因此在此处列出:

 package javax0.blog.demo.throwable.v2;
 public class NumberedLineEmpty extends LineEmpty {

    final protected int lineNr; 
    public NumberedLineEmpty( int lineNr, LineEmpty cause) {

        super (cause);

        this .lineNr = lineNr;

    }

    @Override

    public String getMessage() {

        return "line " + lineNr + ". has zero length" ;

    }
 }

我们将行号存储在int字段中,该字段为final 。 我们这样做是因为

  • 如果可能,使用final变量
  • 如果可能,在对象上使用基元
  • 尽可能长时间以原始形式存储信息,因此不限制其使用

前两个标准是通用的。 尽管不是特定于异常处理,但在这种情况下,最后一个是特殊的。 但是,当我们处理异常时,仅生成包含行号的消息而不是使异常类的结构复杂化是非常有利可图的。 毕竟,我们永远不会的推理除了将异常打印到屏幕上之外,将异常用于任何其他用途。 或不? 这取决于。 首先,永不言败。 再三考虑:如果我们将行号编码到消息中,那么可以肯定的是,除了将其打印给用户之外,我们绝不会将其用于任何其他用途。 那是因为我们不能将其用于其他任何用途。 我们限制自己。 今天的程序员限制了将来的程序员对数据做有意义的事情。

您可能会争辩说这是YAGNI 。 当我们要使用它时,我们应该关心将行号存储为整数,并且在此刻关心它还为时过早,这只是浪费时间。 你是对的! 同时,创建额外字段和计算异常信息的文本版本的getMessage()方法的人也是正确的。 有时,YAGNI与精心设计的良好风格之间的界限很细。 YAGNI是为了避免以后不再需要的复杂代码(除了在创建代码时,您认为自己会需要)。 在此示例中,我认为上述带有一个额外的int字段的异常不是“复杂”的。

我们在“项目”级别有一个类似的代码,在这里我们处理所有文件。 ProjectWftCounter的代码将是

 package javax0.blog.demo.throwable.v2;
 import javax0.blog.demo.throwable.FileLister;
 public class ProjectWftCounter {

    // some lines deleted ...

    public int count() {

        final var fileNames = fileLister.list();

        int sum = 0 ;

        for ( final var fileName : fileNames) {

            try {

                sum += new FileWtfCounter( new FileReader(fileName)).count();

            } catch (NumberedLineEmpty nle) {

                throw new FileNumberedLineEmpty(fileName, nle);

            }

        }

        return sum;

    }
 }

在这里,我们知道文件的名称,因此我们可以扩展信息,将其添加到异常中。

FileNumberedLineEmpty异常也类似于NumberedLineEmpty的代码。 这是FileNumberedLineEmpty的代码:

 package javax0.blog.demo.throwable.v2;
 public class FileNumberedLineEmpty extends NumberedLineEmpty {

    final protected String fileName; 
    public FileNumberedLineEmpty(String fileName, NumberedLineEmpty cause) {

        super (cause.lineNr, cause);

        this .fileName = fileName;

    }

    @Override

    public String getMessage() {

        return fileName + ":" + lineNr + " is empty" ;

    }
 }

现在,我将吸引您关注这样一个事实,即我们创建的异常也属于继承层次结构。 随着我们收集和存储的信息的扩展,它们扩展了另一个,因此:

FileNumberedLineEmpty - extends -> NumberedLineEmpty - extends -> LineEmpty

如果使用这些方法的代码期望并尝试处理LineEmpty异常,那么即使我们抛出更详细和专门的异常,它也可以执行。 如果代码想要使用额外的信息,那么最终,它必须知道实际实例不是LineEmpty而是更专门的NumberedLineEmptyFileNumberedLineEmpty 。 但是,如果只想打印出来,得到消息,则可以将异常作为LineEmpty的实例来处理是绝对好的。 即使这样,由于OO编程多态性,消息仍将包含人类可读形式的额外信息。

吃的时候就是布丁的证明。 我们可以通过简单的测试运行代码。 测试代码与以前的版本相同,唯一的例外是预期的异常类型为FileNumberedLineEmpty而不是LineEmpty 。 但是,打印输出很有趣:

 javax0.blog.demo.throwable.v2.FileNumberedLineEmpty: c.txt:4 is empty

    at javax0.blog.demo.throwable.v2.ProjectWftCounter.count(ProjectWftCounter.java:22)

    at javax0.blog.demo.throwable.v2.TestWtfCounter.lambda$testThrowing$0(TestWtfCounter.java:17)

    at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:62)
 ...

    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
 Caused by: javax0.blog.demo.throwable.v2.NumberedLineEmpty: line 4. has zero length

    at javax0.blog.demo.throwable.v2.FileWtfCounter.count(FileWtfCounter.java:21)

    at javax0.blog.demo.throwable.v2.ProjectWftCounter.count(ProjectWftCounter.java:20)

    ... 68 more ... 68
 Caused by: javax0.blog.demo.throwable.v2.LineEmpty: There is a zero length line

    at javax0.blog.demo.throwable.v2.LineWtfCounter.count(LineWtfCounter.java:15)

    at javax0.blog.demo.throwable.v2.FileWtfCounter.count(FileWtfCounter.java:19)

    ... 69 more ... 69

我们可以对这个结果感到满意,因为我们立即看到引起问题的文件是c.txt ,第四行是罪魁祸首。 另一方面,当我们想看看引发异常的代码时,我们不会感到高兴。 在将来的某个时候,我们可能不记得为什么一条线的长度不能为零。 在这种情况下,我们想看一下代码。 在那里,我们只会看到捕获并重新抛出异常。 幸运的是,这是有原因的,但是实际上直到到达LineWtfCounter.java:15真正的问题的代码为止,这实际上是三个步骤。

有人会对捕获和抛出异常的代码感兴趣吗? 也许是吧。 也许没有。 在我们的案例中,我们决定将不会有人对该代码感兴趣,而不是处理一长串列出有罪原因的异常链,而是将异常的堆栈跟踪更改为引发异常的堆栈跟踪例外。

版本3,设置堆栈跟踪

在此版本中,我们仅更改以下两个异常的代码: NumberedLineEmptyFileNumberedLineEmpty 。 现在,他们不仅扩展了彼此,又扩展了另一个LineEmpty而且还将自己的堆栈跟踪设置为引起异常的值。

这是NumberedLineEmpty的新版本:

package javax0.blog.demo.throwable.v3; public class NumberedLineEmpty extends LineEmpty {

    final protected int lineNr; 
    public NumberedLineEmpty( int lineNr, LineEmpty cause) {

        super (cause);

        this .setStackTrace(cause.getStackTrace());

        this .lineNr = lineNr;

    }

    // getMessage() same as in v2

    @Override

    public Throwable fillInStackTrace() {

        return this ;

    }
 }

这是FileNumberedLineEmpty的新版本:

package javax0.blog.demo.throwable.v3; public class FileNumberedLineEmpty extends NumberedLineEmpty {

    final protected String fileName; 
    public FileNumberedLineEmpty(String fileName, NumberedLineEmpty cause) {

        super (cause.lineNr, cause);

        this .setStackTrace(cause.getStackTrace());

        this .fileName = fileName;

    }

    // getMessage(), same as in v2

    @Override

    public Throwable fillInStackTrace() {

        return this ;

    }
 }

有一个公共的setStackTrace()方法,可用于设置异常的堆栈跟踪。 有趣的是,此方法实际上是public ,不受保护。 该方法是public这一事实意味着可以从外部设置任何异常的堆栈跟踪。 这样做(可能)违反了封装规则。不过,它在那里,如果存在,那么我们可以使用它来将异常的堆栈跟踪设置为与引起异常的堆栈跟踪相同。

这些异常类中还有另一段有趣的代码。 这是公共的fillInStackTrace()方法。 如果像上面这样实现,那么我们可以节省异常在对象构造过程中花费的时间,以收集其自己的原始堆栈跟踪信息,无论如何我们将其替换并丢弃。

当我们创建一个新的异常时,构造函数会调用一个本机方法来填充堆栈跟踪。 如果查看类java.lang.Throwable的默认构造函数,您会发现实际上这就是它的全部功能(Java 14 OpenJDK):

 public Throwable() {

    fillInStackTrace();
 }

方法fillInStackTrace()不是本机的,但这是实际上调用完成工作的本机fillInStackTrace(int)方法的方法。 这是完成的过程:

 public synchronized Throwable fillInStackTrace() {

    if (stackTrace != null ||

        backtrace != null /* Out of protocol state */ ) {

        fillInStackTrace( 0 );

        stackTrace = UNASSIGNED_STACK;

    }

    return this ;
 }

它里面有一些“魔术”,它如何设置字段stackTrace但是到目前为止,这并不真正重要。 但是,请务必注意,方法fillInStackTrace()public 。 这意味着它可以被覆盖。 (为此, protected就足够了,但public更是允许。)

我们还设置了引起异常,在这种情况下,它将具有相同的堆栈跟踪。 运行测试(类似于我们之前只列出其中一项的测试),我们将打印出堆栈:

 javax0.blog.demo.throwable.v3.FileNumberedLineEmpty: c.txt:4 is empty

    at javax0.blog.demo.throwable.v3.LineWtfCounter.count(LineWtfCounter.java:15)

    at javax0.blog.demo.throwable.v3.FileWtfCounter.count(FileWtfCounter.java:16)

    at javax0.blog.demo.throwable.v3.ProjectWftCounter.count(ProjectWftCounter.java:19)

    at javax0.blog.demo.throwable.v3.TestWtfCounter.lambda$testThrowing$0(TestWtfCounter.java:17)

    at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:62)
 ...

    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)

    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
 Caused by: javax0.blog.demo.throwable.v3.NumberedLineEmpty: line 4. has zero length

    ... 71 more ... 71
 Caused by: javax0.blog.demo.throwable.v3.LineEmpty: There is a zero length line

    ... 71 more ... 71

毫不奇怪,我们有一个FileNumberedLineEmpty ,它具有从代码行LineWtfCounter.java:15开始LineWtfCounter.java:15不会引发该异常的堆栈跟踪。 当我们看到这一点时,可能会有一些辩论:

  • 当我们覆盖堆栈跟踪时,为什么我们需要在原始文件上附加引起异常的原因? (我们不。)
  • 这是一个干净的解决方案吗? 堆栈跟踪源自没有引发该异常的行可能会造成混淆。

让我们回答这些问题,是的,出于演示目的,它们是必需的。在实际的应用程序中,每个程序员都可以决定是否要使用这样的解决方案。

这是我们可以获得的最佳解决方案吗? 可能不是,因为正如我所承诺的,我们拥有该应用程序的第四个版本。

版本4,抑制异常

当我们创建模拟FileReader我们非常乐观。 我们假设只有一行的长度为零。 如果有不止一个这样的行怎么办? 在这种情况下,应用程序将从第一个停止。 用户修复了以下错误:要么在行中添加了一些字符,以使该字符不再为空,要么完全删除了该错误,从而使该字符不再为行。 然后,用户再次运行该应用程序以获取异常中的第二个位置。 如果有很多这样的行要纠正,那么此过程可能很麻烦。 您还可以想象真实的应用程序中的代码可能运行很长时间,更不用说花费几个小时了。 仅为了获得问题的下一个位置而执行该应用程序就是在浪费时间,浪费CPU时钟,能源,从而不必要地清洁产生氧气的CO2。

我们可以做的是,更改应用程序,以便在有空行的情况下继续处理该应用程序,并且仅在处理完所有文件和所有行之后,它才会引发异常,列出所有在过程中发现的空行。 有两种方法。 一种是创建一些数据结构并将信息存储在其中,然后在处理结束时,应用程序可以查看该结构,并在其中存在有关某些空行的任何信息时引发异常。 另一种是使用异常类提供的结构来存储信息。

好处是使用异常类提供的结构是

  • 结构已经在那里,不需要重新发明轮子,
  • 它是由许多经验丰富的开发人员精心设计的,并且已经使用了数十年,可能是正确的结构,
  • 该结构具有足够的通用性,可以容纳其他类型的异常,不仅是我们当前拥有的异常,而且数据结构不需要任何更改。

让我们讨论最后一点。 稍后可能发生的情况是,我们决定包含WTF所有资本的行也是例外的,应该抛出异常。 在这种情况下,如果我们决定手工制作这些结构,则可能需要修改存储这些错误情况的数据结构。 如果我们使用Throwable类的抑制的异常,则没有其他事情要做。 有一个异常,我们将其捕获(如您将在示例中很快看到的那样),将其存储,然后将其作为抑制的异常附加到摘要异常的末尾。 当这个演示应用程序极不可能扩展时,我们是否会考虑YAGNI? 是的,不是,通常没有关系。 当您花时间和精力过早开发某些东西时,YAGNI通常是一个问题。 在开发中以及后来的维护中,这是一笔额外的费用。 当我们只使用已经存在的更简单的东西时,不是YAGNI使用它。 它对我们使用的工具非常聪明并且知识渊博。

让我们看一下修改后的FileReader ,这次它已经在许多文件中返回许多空行:

 package javax0.blog.demo.throwable.v4;
 import java.io.FileNotFoundException;
 import java.util.List;
 public class FileReader {

    final String fileName; 
    public FileReader(String fileName) {

        this .fileName = fileName;

    }

    public List<String> list() {

        if (fileName.equals( "a.txt" )) {

            return List.of( "wtf wtf" , "wtf something" , "" , "nothing" );

        }

        if (fileName.equals( "b.txt" )) {

            return List.of( "wtf wtf wtf" , "" , "wtf something wtf" , "nothing wtf" , "" );

        }

        if (fileName.equals( "c.txt" )) {

            return List.of( "wtf wtf wtf" , "" , "wtf something wtf" , "nothing wtf" , "" );

        }

        throw new RuntimeException( "File is not found: " + fileName);

    }
 }

现在,所有三个文件都包含空行。 我们不需要修改LineWtfCounter计数器。 空行时,我们抛出异常。 在此级别上,没有任何方法可以抑制此异常。 我们无法在此处收集任何例外列表。 我们只关注可能为空的一行。

FileWtfCounter的情况不同:

 package javax0.blog.demo.throwable.v4;
 public class FileWtfCounter {

    private final FileReader fileReader; 
    public FileWtfCounter(FileReader fileReader) {

        this .fileReader = fileReader;

    }

    public int count() {

        final var lines = fileReader.list();

        NumberedLinesAreEmpty exceptionCollector = null ;

        int sum = 0 ;

        int lineNr = 1 ;

        for ( final var line : lines) {

            try {

                sum += new LineWtfCounter(line).count();

            } catch (LineEmpty le){

                final var nle = new NumberedLineEmpty(lineNr,le);

                if ( exceptionCollector == null ){

                    exceptionCollector = new NumberedLinesAreEmpty();

                }

                exceptionCollector.addSuppressed(nle);

            }

            lineNr ++;

        }

        if ( exceptionCollector != null ){

            throw exceptionCollector;

        }

        return sum;

    }
 }

当我们捕获LineEmpty异常时,我们将其存储在局部变量exceptionCollector引用的聚合exceptionCollector 。 如果没有exceptionCollector那么我们在添加捕获的异常之前先创建一个,以避免NPE。 在处理的最后,当我们处理所有行时,我们可能将许多异常添加到摘要异常exceptionCollector 。 如果存在,则将其抛出。

同样, ProjectWftCounter收集由不同FileWtfCounter实例引发的所有异常,并且在处理结束时,它引发摘要异常,如以下代码行所示:

 package javax0.blog.demo.throwable.v4;
 import javax0.blog.demo.throwable.FileLister;
 public class ProjectWftCounter { 
    private final FileLister fileLister; 
    public ProjectWftCounter(FileLister fileLister) {

        this .fileLister = fileLister;

    }

    public int count() {

        final var fileNames = fileLister.list();

        FileNumberedLinesAreEmpty exceptionCollector = null ;

        int sum = 0 ;

        for ( final var fileName : fileNames) {

            try {

                sum += new FileWtfCounter( new FileReader(fileName)).count();

            } catch (NumberedLinesAreEmpty nle) {

                if ( exceptionCollector == null ){

                    exceptionCollector = new FileNumberedLinesAreEmpty();

                }

                exceptionCollector.addSuppressed(nle);

            }

        }

        if ( exceptionCollector != null ){

            throw exceptionCollector;

        }

        return sum;

    }
 }

现在,我们已经将所有有问题的行收集到一个巨大的异常结构中,我们应该得到一个堆栈跟踪:

 javax0.blog.demo.throwable.v4.FileNumberedLinesAreEmpty: There are empty lines

    at javax0.blog.demo.throwable.v4.ProjectWftCounter.count(ProjectWftCounter.java:24)

    at javax0.blog.demo.throwable.v4.TestWtfCounter.lambda$testThrowing$0(TestWtfCounter.java:17)

    at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:62)

    at org.assertj.core.api.AssertionsForClassTypes.catchThrowable(AssertionsForClassTypes.java:750)

    at org.assertj.core.api.Assertions.catchThrowable(Assertions.java:1179)

    at javax0.blog.demo.throwable.v4.TestWtfCounter.testThrowing(TestWtfCounter.java:15)

    at java.base /jdk .internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

    at java.base /jdk .internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

    at java.base /jdk .internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

    at java.base /java .lang.reflect.Method.invoke(Method.java:564)

    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)

    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)

    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)

    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)

    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)

    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)

    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)

    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)

    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)

    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)

    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)

    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)

    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)

    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)

    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:205)

    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)

    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:201)

    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)

    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)

    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)

    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)

    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)

    at java.base /java .util.ArrayList.forEach(ArrayList.java:1510)

    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)

    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)

    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)

    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)

    at java.base /java .util.ArrayList.forEach(ArrayList.java:1510)

    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)

    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)

    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)

    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)

    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)

    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)

    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)

    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)

    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)

    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)

    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)

    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)

    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)

    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)

    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)

    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)

    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

    Suppressed: javax0.blog.demo.throwable.v4.NumberedLinesAreEmpty

        at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:22)

        at javax0.blog.demo.throwable.v4.ProjectWftCounter.count(ProjectWftCounter.java:21)

        ... 68 more ... 68

        Suppressed: javax0.blog.demo.throwable.v4.NumberedLineEmpty: line 3.

            at javax0.blog.demo.throwable.v4.LineWtfCounter.count(LineWtfCounter.java:15)

            at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:18)

            ... 69 more ... 69

        Caused by: javax0.blog.demo.throwable.v4.LineEmpty: There is a zero length line

    Suppressed: javax0.blog.demo.throwable.v4.NumberedLinesAreEmpty

        at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:22)

        at javax0.blog.demo.throwable.v4.ProjectWftCounter.count(ProjectWftCounter.java:21)

        ... 68 more ... 68

        Suppressed: javax0.blog.demo.throwable.v4.NumberedLineEmpty: line 2.

            at javax0.blog.demo.throwable.v4.LineWtfCounter.count(LineWtfCounter.java:15)

            at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:18)

            ... 69 more ... 69

        Caused by: javax0.blog.demo.throwable.v4.LineEmpty: There is a zero length line

        Suppressed: javax0.blog.demo.throwable.v4.NumberedLineEmpty: line 5.

            at javax0.blog.demo.throwable.v4.LineWtfCounter.count(LineWtfCounter.java:15)

            at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:18)

            ... 69 more ... 69

        Caused by: javax0.blog.demo.throwable.v4.LineEmpty: There is a zero length line

    Suppressed: javax0.blog.demo.throwable.v4.NumberedLinesAreEmpty

        at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:22)

        at javax0.blog.demo.throwable.v4.ProjectWftCounter.count(ProjectWftCounter.java:21)

        ... 68 more ... 68

        Suppressed: javax0.blog.demo.throwable.v4.NumberedLineEmpty: line 2.

            at javax0.blog.demo.throwable.v4.LineWtfCounter.count(LineWtfCounter.java:15)

            at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:18)

            ... 69 more ... 69

        Caused by: javax0.blog.demo.throwable.v4.LineEmpty: There is a zero length line

        Suppressed: javax0.blog.demo.throwable.v4.NumberedLineEmpty: line 5.

            at javax0.blog.demo.throwable.v4.LineWtfCounter.count(LineWtfCounter.java:15)

            at javax0.blog.demo.throwable.v4.FileWtfCounter.count(FileWtfCounter.java:18)

            ... 69 more ... 69

        Caused by: javax0.blog.demo.throwable.v4.LineEmpty: There is a zero length line

这次,我没有删除任何线条以使您感觉到它在肩上的重量。 现在您可能会开始考虑使用异常结构而不是仅包含我们所需信息的整洁,苗条的专用数据结构是否值得。 如果您开始这样认为,那就停止它。 不要这样问题(如果有的话)不是我们有太多信息。 问题在于我们的表达方式。 为了克服它,解决方案不是将婴儿洗澡水倒掉……多余的信息,而是以更具可读性的方式表示出来。 如果应用程序很少遇到许多空行,那么通过堆栈跟踪进行读取对于用户而言可能不是一个难以承受的负担。 如果这是一个经常出现的问题,并且您希望对用户(客户,支付账单的用户)友好,那么,也许不错的异常结构打印机是一个不错的解决方案。

我们在项目中实际上有一个适合您

javax0.blog.demo.throwable.v4.ExceptionStructurePrettyPrinter

您可以随意使用甚至修改。 这样,以前的“可怕”堆栈跟踪的打印输出将打印为:

 FileNumberedLinesAreEmpty( "There are empty lines" )

    Suppressed: NumberedLineEmpty( "line 3." )

      Caused by:LineEmpty( "There is a zero length line" )

    Suppressed: NumberedLineEmpty( "line 2." )

      Caused by:LineEmpty( "There is a zero length line" )

    Suppressed: NumberedLineEmpty( "line 5." )

      Caused by:LineEmpty( "There is a zero length line" )

    Suppressed: NumberedLineEmpty( "line 2." )

      Caused by:LineEmpty( "There is a zero length line" )

    Suppressed: NumberedLineEmpty( "line 5." )

      Caused by:LineEmpty( "There is a zero length line" )

这样,我们就结束了练习。 我们逐步完成了以下步骤:从v1简单地引发和捕获异常,到v2设置导致异常的娃套风格, v3更改嵌入异常的堆栈跟踪,最后v4存储所有在过程中收集的抑制的异常。 您现在可以做的是下载项目,进行操作,检查堆栈跟踪,修改代码,等等。 或者继续阅读,我们有一些有关异常的额外信息,这些基本级教程很少讨论这些异常,也值得阅读最后的总结部分。

有关异常的其他注意事项

在本节中,我们将告诉您一些关于异常的基本Java教程中并不为人们所熟知的信息,而这些信息通常是缺失的。

JVM中没有检查异常之类的东西

除非方法声明明确指出可能会发生这种情况,否则无法从Java方法中引发已检查的异常。 有趣的是,JVM不了解检查异常的概念。 这是由Java编译器处理的,但是当代码进入JVM时,不会对此进行检查。

 Throwable (checked) <-- Exception (checked) <-- RuntimeException (unchecked)

                                            <-- Other Exceptions (checked)

                    <-- Error (unchecked)

异常类的结构如上所述。 异常的根类是Throwable 。 Any object that is an instance of a class, which extends directly or indirectly the Throwable class can be thrown. The root class Throwable is checked, thus if an instance of it is thrown from a method, then it has to be declared.
If any class extends this class directly and is thrown from a method then, again it has to be declared. Except if the object is also an instance of RuntimeException or Error . In that case the exception or error is not checked and can be thrown without declaring on the throwing method.

The idea of checked exception is controversial. There are advantages of its use but there are many languages that do not have the notion of it. This is the reason why the JVM does not enforce the declaration of checked exceptions. If it did it would not be possible reasonably to generate JVM code from languages that do not require exceptions declared and want to interoperate with the Java exceptions. Checked exceptions also cause a lot of headaches when we are using streams in Java.

It is possible to overcome of checked exceptions. A method created with some hack, or simply in a JVM language other than Java can throw a checked exception even if the method does not declare the exception to be thrown. The hacky way uses a simple static utility method, as listed in the following code snippet:

 package javax0.blog.demo.throwable.sneaky;
 public class SneakyThrower {

    public static <E extends Throwable> E throwSneaky(Throwable e) throws E {

        throw (E) e;

    }
 }

When a code throws a checked exception, for example Exception then passing it to throwSneaky() will fool the compiler. The compiler will look at the declaration of the static method and cannot decide if the Throwable it throws is checked or not. That way it will not require the declaration of the exception in the throwing method.

The use of this method is very simple and is demonstrated with the following unit test code:

 package javax0.blog.demo.throwable.sneaky;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import static javax0.blog.demo.throwable.sneaky.SneakyThrower.throwSneaky;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;
 public class TestSneaky { 
    @DisplayName ( "Can throw checked exception without declaring it" )

    @Test

    void canThrowChecked() {

        class FlameThrower {

            void throwExceptionDeclared() throws Exception {

                throw new Exception();

            }

            void throwExceptionSecretly() {

                throwSneaky( new Exception());

            }

        }

        final var sut = new FlameThrower();

        assertThat(catchThrowable(() -> sut.throwExceptionDeclared())).isInstanceOf(Exception. class );

        assertThat(catchThrowable(() -> sut.throwExceptionSecretly())).isInstanceOf(Exception. class );

    }

    int doesNotReturn(){

        throw throwSneaky( new Exception());

        // no need for a return command

    }
 }

The two methods throwExceptionDeclared() and throwExceptionSecretly() demonstrate the difference between normal and sneaky throwing.

The method throwSneaky() never returns, and it still has a declared return value. The reason for that is to allow the pattern that can be seen in the method doesNotReturn() towards the end of the text code. We know that the method throwSneaky() never returns, but the compiler does not know. If we simply call it then the compiler will still require some return statement in our method. In more complex code flow it may complain about uninitialized variables. On the other hand if we “throw” the return value in the code then it gives the compiler a hint about the execution flow. The actual throwing on this level will never happen actually, but it does not matter.

Never catch Throwable , ...Error or COVID

When we catch an exception we can catch checked exception, RuntimeException or just anything that is Throwable . However, there are other things that are Throwable but are not exceptions and are also not checked. These are errors.

故事:

I do a lot of technical interviews where candidates come and answer my questions. I have a lot of reservations and bad feelings about this. I do not like to play “God”. On the other hand, I enjoy a lot when I meet clever people, even if they are not fit for a given work position. I usually try to conduct the interviews that the value from it is not only the evaluation of the candidate but also something that the candidate can learn about Java, the profession, or just about themselves. There is a coding task that can be solved using a loop, but it lures inexperienced developers to have a solution that is recursive. Many of the developers who create the recursive solution realize that there is no exit condition in their code for some type of the input parameters. (Unless there is because they do it in the clever way. However, when they are experienced enough, they do not go for the recursive solution instead of a simple loop. So when it is a recursive solution they almost never have an exit condition.) What will happen if we run that code with an input parameter that never ends the recursive loop? We get a StackOverflowException . Under the pressure and stress of the interview, many of them craft some code that catches this exception. This is problematic. This is a trap!

Why is it a trap? Because the code will not ever throw a StackOverflowException . There is no such thing in the JDK as StackOverflowException . It is StackOverflowError . It is not an exception, and the rule is that

YOUR CODE MUST NEVER CATCH AN ERROR

The StackOverflowError (not exception) extends the class VirtualMachineError which says in the JavaDoc:

Thrown to indicate that the Java Virtual Machine is broken

When something is broken you can glue it together, mend, fix, but you can never make it unbroken. If you catch a Throwable which is also an instance of Error then the code executing in the catch part is run in a broken VM. What can happen there? Anything and the continuation of the execution may not be reliable.

Never catch an Error !

Summary and Takeaway

In this article we discussed exceptions, specifically:

  • how to throw meaningful exceptions by adding information when it becomes available,
  • how to replace the stack trace of an exception with setTrackTrace() when it makes sense,
  • how to collect exceptions with addSuppressed() when your application can throw exceptions multiple times We also discussed some interesting bits about how the JVM does not know about checked exceptions and why you should never catch an Error .

Don't just (re)throw exceptions when they happen. Think about why and how they happen and handle them appropriately.

Use the information in this article to make your code exceptional 😉

(Code and article were reviewed and proofread by Mihaly Verhas. He also wrote the takeaway section including the last
sentence.)

翻译自: https://www.javacodegeeks.com/2020/05/all-you-wanted-to-know-about-throwable.html

throwable

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值