日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

throwable_您想了解的所有Throwable

發布時間:2023/12/3 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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.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 。 可以拋出作為類實例的任何對象,直接或間接擴展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.html

throwable

總結

以上是生活随笔為你收集整理的throwable_您想了解的所有Throwable的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。