电子商务网站课程设计总结,古典水墨网站,网站建设的目标用户,21世纪上海人才网官网throwable本文是有关异常的教程。 但不是通常的一种。 其中有许多内容可以告诉您异常的含义#xff0c;如何抛出异常#xff0c;捕获异常#xff0c;已检查异常和运行时异常之间的区别#xff0c;等等。 没有必要了。 这对您来说也很无聊。 如果没有#xff0c;那么请阅读… 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 ListString list() { return List.of( a.txt , b.txt , c.txt ); } } 文件系统中有三个文件a.txt b.txt和c.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 ListString 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上发布的源代码则可能会找到这种做法的示例。 我不为他们感到骄傲对不起。 通常我建议异常处理不应干扰应用程序的主数据流。 必须将其分开因为这是一个单独的问题。 解决方案是在多个级别上处理异常在每个级别上添加实际可用的信息。 为此我们修改了FileWtfCounter和ProjectWftCounter类。 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而是更专业的NumberedLineEmpty或FileNumberedLineEmpty 。 但是如果只想将其打印出来则获得消息然后将异常作为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设置堆栈跟踪 在此版本中我们仅更改以下两个异常的代码 NumberedLineEmpty和FileNumberedLineEmpty 。 现在他们不仅扩展了彼此又扩展了另一个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 不会引发该异常。 当我们看到这一点时可能会有一些辩论 当我们覆盖堆栈跟踪时为什么我们需要在原始文件上附加引起异常的原因 我们不。 这是一个干净的解决方案吗 堆栈跟踪源自没有引发该异常的行可能会造成混淆。 让我们回答这些问题是的出于演示目的它们是必需的。在实际的应用程序中每个程序员都可以决定是否要使用这样的解决方案。 这是我们可以获得的最佳解决方案吗 可能不是因为正如我所承诺的我们拥有该应用程序的第四版。 版本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 ListString 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 。 可以抛出作为类实例的任何对象直接或间接扩展Throwable类。 将检查根类Throwable 因此如果从方法中抛出了它的实例则必须对其进行声明。 如果任何类直接扩展该类并从方法中抛出则必须再次声明它。 除非对象也是RuntimeException或Error的实例。 在这种情况下不检查异常或错误可以在不声明throwing方法的情况下抛出该异常或错误。 检查异常的想法是有争议的。 使用它有很多优点但是有许多语言没有它的概念。 这就是JVM不强制执行检查异常的声明的原因。 如果这样做的话就不可能从不需要声明的异常并且想要与Java异常互操作的语言中生成JVM代码。 当我们在Java中使用流时检查异常也会引起很多麻烦。 可以克服检查的异常。 使用某种技巧创建的方法或者仅使用Java以外的JVM语言创建的方法即使该方法未声明要抛出的异常也可以抛出已检查的异常。 hacky方式使用一种简单的static实用程序方法如以下代码片段所示 package javax0.blog.demo.throwable.sneaky; public class SneakyThrower { public static E extends Throwable E throwSneaky(Throwable e) throws E { throw (E) e; } } 当代码引发一个检查异常时例如Exception然后将其传递给throwSneaky()将使编译器傻瓜。 编译器将查看静态方法的声明并且无法决定是否检查其抛出的Throwable 。 这样它将不需要在throwing方法中声明异常。 此方法的使用非常简单并通过以下单元测试代码进行了演示 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 } } 两种方法throwExceptionDeclared()和throwExceptionSecretly()演示了正常抛出和偷偷摸摸的抛出之间的区别。 throwSneaky()方法从不返回并且仍然具有声明的返回值。 这样做的原因是允许在方法doesNotReturn()可以看到的模式接近文本代码的结尾。 我们知道方法throwSneaky()从不返回但是编译器不知道。 如果我们简单地调用它则编译器仍将在我们的方法中需要一些return语句。 在更复杂的代码流中它可能会抱怨未初始化的变量。 另一方面如果我们在代码中“抛出”返回值那么它将为编译器提供有关执行流程的提示。 实际不会在此级别上进行实际投掷但这无关紧要。 永远不要抓 当我们捕获异常时我们可以捕获检查异常 RuntimeException或任何Throwable 。 不过也有其他的东西是Throwable 但都没有异常也不会检查。 这些是错误。 故事 我进行了很多技术面试应聘者会来回答我的问题。 我对此有很多保留和不好的感觉。 我不喜欢玩“上帝”。 另一方面当我遇到聪明的人时即使他们不适合给定的工作职位我也会很享受。 我通常尝试进行面试从中获得的价值不仅是对候选人的评价而且是候选人可以从中了解Java专业或自己的东西。 有一个可以使用循环解决的编码任务但是它诱使没有经验的开发人员拥有递归的解决方案。 许多创建递归解决方案的开发人员意识到对于某些类型的输入参数代码中没有退出条件。 除非是因为他们以聪明的方式这样做。但是当他们有足够的经验时他们不会寻求递归解决方案而不是简单的循环。因此当它是递归解决方案时它们几乎永远不会有退出条件。 如果我们使用永不结束递归循环的输入参数运行该代码将会发生什么 我们得到一个StackOverflowException 。 在面试的压力和压力下他们中的许多人编写了一些捕获此异常的代码。 这是有问题的。 这是一个陷阱 为什么是陷阱 因为代码永远不会抛出StackOverflowException 。 JDK中没有StackOverflowException这样的东西。 它是StackOverflowError 。 这也不例外规则是 您的代码绝不能出错 StackOverflowError 并非例外扩展了VirtualMachineError类该类在JavaDoc中说 抛出以表明Java虚拟机已损坏 发生故障时您可以将其粘合在一起进行修补修复但是您永远都不能使其断裂。 如果捕获的Throwable也是Error的实例则在catch部分执行的代码将在损坏的VM中运行。 那里会发生什么 任何事情以及执行的继续可能都不可靠。 永远不要发现Error 摘要和总结 在本文中我们讨论了异常特别是 如何通过在可用时添加信息来引发有意义的异常 在setTrackTrace()的情况下如何使用setTrackTrace()替换异常的堆栈跟踪 当您的应用程序可以多次抛出异常时如何使用addSuppressed()收集异常我们还讨论了一些有趣的知识关于JVM如何不了解已检查的异常以及为什么永远不应该捕获Error 。 不要只是在异常发生时重新抛出异常。 考虑一下它们为什么发生以及如何发生并适当地处理它们。 使用本文中的信息使您的代码与众不同 代码和文章由Mihaly Verhas进行了审核和校对。他还写了外卖部分包括最后一篇 句子。 翻译自: https://www.javacodegeeks.com/2020/05/all-you-wanted-to-know-about-throwable.htmlthrowable