您想了解的所有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.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 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上發布的源代碼,則可能會找到這種做法的示例。 我不為他們感到驕傲,對不起。
通常,我建議異常處理不應干擾應用程序的主數據流。 必須將其分開,因為這是一個單獨的問題。
解決方案是在多個級別上處理異常,在每個級別上添加實際可用的信息。 為此,我們修改了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 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 !
摘要和總結
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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自然数 素数 质数_在Java中获取素数
- 下一篇: 石化区备案介质整改报告(石化区备案)