throwable_您想了解的所有Throwable
throwable
本文是有關(guān)異常的教程。 但不是通常的一種。 其中有許多內(nèi)容可以告訴您異常的含義,如何拋出異常,捕獲異常,已檢查異常和運(yùn)行時(shí)異常之間的區(qū)別,等等。 沒有必要了。 這對(duì)您來說也很無聊。 如果沒有,那么請(qǐng)閱讀其中的一本,并在您了解他們所教的內(nèi)容后再回來。 本文從這些教程的結(jié)尾處開始。 我們將更深入地研究Java異常,您可以使用它們做什么,應(yīng)該使用它們做什么以及它們可能沒有聽說的功能。 如果setStackTrace() , getCause()和getSuppressed()是您早餐時(shí)使用的方法,則可以跳過本文。 但是,如果不是這樣,并且您想對(duì)此有所了解,請(qǐng)繼續(xù)。 這篇文章很長(zhǎng)。 寫作花了很長(zhǎng)時(shí)間,而閱讀花了很長(zhǎng)時(shí)間。 這是必需的。
介紹
在本文中,我們將討論異常以及Java異常可以做什么以及應(yīng)該做什么。 最簡(jiǎn)單的情況是拋出一個(gè)然后捕獲它,但是存在更復(fù)雜的情況,例如設(shè)置原因或抑制異常。 我們將探討這些可能性,以及更多其他可能性。 為了發(fā)現(xiàn)可能性,我們將開發(fā)一個(gè)簡(jiǎn)單的應(yīng)用程序,并逐步創(chuàng)建四個(gè)版本,進(jìn)一步開發(fā)該應(yīng)用程序,并使用越來越多的異常處理可能性。 源代碼在存儲(chǔ)庫(kù)中可用:
https://github.com/verhas/BLOG/tree/master/exception_no_stack
不同的版本在不同的Java包中。 一些在不同版本中未更改的類要高一包,并且不會(huì)被版本化。
- 第一個(gè)版本v1只會(huì)引發(fā)en異常,并且應(yīng)用程序不會(huì)對(duì)其進(jìn)行處理。 測(cè)試代碼期望測(cè)試設(shè)置拋出異常。 此版本是演示為什么我們需要更復(fù)雜的解決方案的基準(zhǔn)。 我們將體驗(yàn)到,沒有足夠的信息來了解實(shí)際問題發(fā)生在哪里的異常。
- 第二個(gè)版本v2在更高級(jí)別上捕獲了該異常,并引發(fā)了一個(gè)新異常,并提供了有關(guān)異常情況的更多信息,并且新異常中嵌入了原始異常作為原因。 這種方法可以提供足夠的信息來跟蹤問題的位置,但是甚至可以對(duì)其進(jìn)行增強(qiáng),以便于閱讀和識(shí)別實(shí)際問題。
- v3將演示我們?nèi)绾涡薷男庐惓5膭?chuàng)建,以便更高級(jí)別的異常的堆棧跟蹤不會(huì)指向捕獲原始異常的位置,而是指向引發(fā)原始異常的位置。
- 最后,第四版v4將演示在異常情況下即使可能無法成功完成操作也可以繼續(xù)處理時(shí)如何抑制表達(dá)式。 這種“更進(jìn)一步”使最后可能有一個(gè)異常,該異常收集有關(guān)所有發(fā)現(xiàn)的特殊情況的信息,而不僅僅是第一次出現(xiàn)的信息。
如果您查看代碼,還將在此找到本文的原始文本,以及有助于維護(hù)代碼段的設(shè)置,這些代碼段將其從源代碼復(fù)制到文章中,從而使所有代碼段都保持最新。 對(duì)我們有用的工具是Java :: Geci。
樣品申請(qǐng)
我們使用異常來處理超出程序正常流程的范圍。 引發(fā)異常時(shí),程序的正常流程將中斷,并且執(zhí)行將停止將異常轉(zhuǎn)儲(chǔ)到某些輸出。 也可以使用語(yǔ)言中內(nèi)置的try and catch命令對(duì)來捕獲這些異常。
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中的對(duì)象,并且可以包含很多信息。 當(dāng)我們?cè)诖a中捕獲異常時(shí),我們可以訪問異常對(duì)象,并且代碼可以在特殊情況下也可以訪問異常對(duì)象所攜帶的參數(shù),從而采取行動(dòng)。 可以實(shí)現(xiàn)我們自己的擴(kuò)展Java的異常
java.lang.Throwable類或直接或傳遞擴(kuò)展Throwable某些類。 (通常,我們擴(kuò)展Exception類。)我們自己的實(shí)現(xiàn)可以包含許多描述異常情況性質(zhì)的參數(shù)。 我們?yōu)榇四康氖褂脤?duì)象字段。
盡管異常可以攜帶的數(shù)據(jù)沒有限制,但通常所包含的信息和堆棧跟蹤不超過一個(gè)。 在Throwable類中定義了其他參數(shù)的空間,例如導(dǎo)致當(dāng)前參數(shù)的異常( getCause() )或一系列抑制異常( getSuppressed() )。 很少使用它們,可能是因?yàn)殚_發(fā)人員不了解這些功能,并且因?yàn)榇蠖鄶?shù)情況很簡(jiǎn)單,不需要這些可能性。 我們將在本文中介紹這些可能性,以使您不屬于僅因?yàn)樗麄儾涣私膺@些方法而不使用這些方法的無知開發(fā)人員。
我們有一個(gè)示例應(yīng)用程序。 它不僅僅是在catch分支中引發(fā),捕獲和處理異常,該異常使代碼得以繼續(xù)。 這很簡(jiǎn)單,并且在您第一次學(xué)習(xí)Java編程時(shí)已閱讀的教程中對(duì)此進(jìn)行了解釋。
我們的示例應(yīng)用程序?qū)⒏訌?fù)雜。 我們將在目錄中列出文件,讀取行,并計(jì)算wtf字符串的數(shù)量。 通過這種方式,我們可以自動(dòng)執(zhí)行代碼審查過程質(zhì)量評(píng)估(開玩笑)。 可以說,代碼質(zhì)量與代碼審查期間的WTF數(shù)量成反比。
解決方案包含
- 可以列出文件的FileLister ,
- 可以讀取文件的FileReader ,
- 一個(gè)LineWtfCounter ,它將在一行中計(jì)算wtf ,
- 一個(gè)FileWtfCounter ,它將使用上一個(gè)類對(duì)列出行的整個(gè)文件中的所有wtf進(jìn)行計(jì)數(shù),最后,
- 一個(gè)ProjectWtfCounter ,它使用文件級(jí)計(jì)數(shù)器對(duì)整個(gè)項(xiàng)目中的wtf進(jìn)行計(jì)數(shù),列出所有文件。
版本1,投擲
該應(yīng)用程序的功能非常簡(jiǎn)單,并且因?yàn)槲覀儗W⒂诋惓L幚?#xff0c;所以實(shí)現(xiàn)也很簡(jiǎn)單。 例如,文件列表類很簡(jiǎn)單,如下所示:
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" ); } }文件系統(tǒng)中有三個(gè)文件a.txt , b.txt和c.txt 。 當(dāng)然,這是一個(gè)模擬,但是在這種情況下,我們不需要更復(fù)雜的方法來演示異常處理。 同樣, FileReader也是一種模擬實(shí)現(xiàn),僅用于演示目的:
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); } }計(jì)算一行中wtf出現(xiàn)次數(shù)的計(jì)數(shù)器是
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 } }為了節(jié)省空間并專注于我們的主題,代碼段不顯示實(shí)際的邏輯(由Java :: Geci自動(dòng)刪除)。 讀者可以創(chuàng)建一個(gè)代碼,該代碼實(shí)際計(jì)算字符串中wtf子字符串的數(shù)量,或者簡(jiǎn)單地計(jì)算“ wtf”。 即使讀者不能編寫這樣的代碼,也可以從本文開頭提到的存儲(chǔ)庫(kù)中獲得。
我們應(yīng)用程序中的邏輯說,如果文件中的一行長(zhǎng)度為零,則這是一種特殊情況。 在這種情況下,我們拋出異常。
通常,這種情況并不能證明是一個(gè)例外,我承認(rèn)這是一個(gè)虛構(gòu)的示例,但是我們需要一些簡(jiǎn)單的方法。 如果行的長(zhǎng)度為零,則拋出LineEmpty異常。 (我們沒有列出LineEmpty異常的代碼。它在代碼存儲(chǔ)庫(kù)中,它很簡(jiǎn)單,沒什么特別的。它擴(kuò)展了RuntimeException ,無需聲明我們將其放在何處。)如果您查看FileReader的模擬實(shí)現(xiàn),則您會(huì)看到我們?cè)谖募.txt中插入了空行。
使用行級(jí)計(jì)數(shù)器的文件級(jí)計(jì)數(shù)器如下:
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; } }(同樣,從打印輸出中跳過了一些瑣碎的行。)
這是該應(yīng)用程序的第一個(gè)版本。 它沒有任何特殊的異常處理。 它只是對(duì)行計(jì)數(shù)器返回的值求和,如果較低級(jí)別有異常,則行wtf計(jì)數(shù)器會(huì)自動(dòng)向上傳播。 在此級(jí)別上,我們不會(huì)以任何方式處理該異常。
項(xiàng)目級(jí)別計(jì)數(shù)器非常相似。 它使用文件計(jì)數(shù)器并對(duì)結(jié)果求和。
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; } }我們使用簡(jiǎn)單的測(cè)試代碼對(duì)其進(jìn)行測(cè)試:
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(); } }單元測(cè)試通常不應(yīng)具有堆棧跟蹤打印。 在這種情況下,我們可以演示所拋出的內(nèi)容。 錯(cuò)誤中的堆棧跟蹤將向我們顯示以下錯(cuò)誤:
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)這個(gè)異常有點(diǎn)問題。 當(dāng)我們使用此代碼時(shí),它不會(huì)告訴我們有關(guān)有問題的實(shí)際文件和行的任何信息。 如果有一個(gè)空文件,我們必須檢查所有文件和所有行。 為此編寫一個(gè)應(yīng)用程序并不是太困難,但是我們不想代替創(chuàng)建該應(yīng)用程序的程序員來工作。 如果有例外,我們希望該例外能夠?yàn)槲覀兲峁┳銐虻男畔?#xff0c;以成功解決該情況。 應(yīng)用程序必須告訴我哪個(gè)文件和哪一行有問題。
版本2,設(shè)置原因
為了在異常中提供信息,我們必須收集信息并將其插入異常中。 這是我們?cè)诘诙鎽?yīng)用程序中所做的。
第一個(gè)版本中的異常不包含文件名或行號(hào),因?yàn)榇a未將其放在此處。 該代碼有這樣做的充分理由。 引發(fā)異常的位置的代碼沒有信息,因此無法將其沒有的信息插入異常。
一種有利可圖的方法是將這些信息與其他參數(shù)一起傳遞,以便在發(fā)生異常時(shí)代碼可以將此信息插入到異常中。 我不推薦這種方法。 如果您查看我在GitHub上發(fā)布的源代碼,則可能會(huì)找到這種做法的示例。 我不為他們感到驕傲,對(duì)不起。
通常,我建議異常處理不應(yīng)干擾應(yīng)用程序的主數(shù)據(jù)流。 必須將其分開,因?yàn)檫@是一個(gè)單獨(dú)的問題。
解決方案是在多個(gè)級(jí)別上處理異常,在每個(gè)級(jí)別上添加實(shí)際可用的信息。 為此,我們修改了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; } }該代碼捕獲了向空行發(fā)出信號(hào)的異常并引發(fā)了一個(gè)新的異常,該異常已經(jīng)具有一個(gè)參數(shù):該行的序列號(hào)。
此異常的代碼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" ; } }我們將行號(hào)存儲(chǔ)在int字段中,該字段為final 。 我們這樣做是因?yàn)?
- 盡可能使用final變量
- 如果可能,在對(duì)象上使用基元
- 盡可能長(zhǎng)時(shí)間以原始形式存儲(chǔ)信息,因此不限制其使用
前兩個(gè)標(biāo)準(zhǔn)是通用的。 盡管不是特定于異常處理,但最后一種在這種情況下是特殊的。 但是,當(dāng)我們處理異常時(shí),僅生成包含行號(hào)的消息而不是使異常類的結(jié)構(gòu)復(fù)雜化是非常有利可圖的。 畢竟,我們永遠(yuǎn)不會(huì)的推理
除了將異常打印到屏幕上之外,將異常用于任何其他用途。 或不? 這取決于。 首先,永遠(yuǎn)不要說永遠(yuǎn)。 再三考慮:如果我們將行號(hào)編碼到消息中,那么可以肯定的是,除了將其打印給用戶之外,我們絕不會(huì)將其用于任何其他用途。 那是因?yàn)槲覀儾荒軐⑺糜谄渌魏斡猛尽?我們限制自己。 今天的程序員限制了將來的程序員對(duì)數(shù)據(jù)做有意義的事情。
您可能會(huì)爭(zhēng)辯說這是YAGNI 。 當(dāng)我們要使用它時(shí),我們應(yīng)該關(guān)心將行號(hào)存儲(chǔ)為整數(shù),并且在此刻關(guān)心它還為時(shí)過早,這只是浪費(fèi)時(shí)間。 你是對(duì)的! 同時(shí),創(chuàng)建額外字段和計(jì)算異常信息的文本版本的getMessage()方法的人也是正確的。 有時(shí),YAGNI與精心設(shè)計(jì)的良好風(fēng)格之間的界限很細(xì)。 YAGNI是為了避免以后不再需要的復(fù)雜代碼(除了在創(chuàng)建代碼時(shí),您認(rèn)為自己會(huì)需要)。 在此示例中,我認(rèn)為上述帶有一個(gè)額外的int字段的異常不是“復(fù)雜”的。
我們?cè)凇绊?xiàng)目”級(jí)別有一個(gè)類似的代碼,在這里我們處理所有文件。 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; } }在這里,我們知道文件的名稱,因此我們可以擴(kuò)展信息,將其添加到異常中。
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" ; } }現(xiàn)在,我將吸引您關(guān)注這樣一個(gè)事實(shí),即我們創(chuàng)建的異常也屬于繼承層次結(jié)構(gòu)。 隨著我們收集和存儲(chǔ)的信息的擴(kuò)展,它們擴(kuò)展了另一個(gè),因此:
FileNumberedLineEmpty - extends -> NumberedLineEmpty - extends -> LineEmpty如果使用這些方法的代碼期望并嘗試處理LineEmpty異常,那么即使我們拋出更詳細(xì)和專門的異常,它也可以執(zhí)行。 如果代碼想要使用額外的信息,那么最終,它必須知道實(shí)際實(shí)例不是LineEmpty而是更專業(yè)的NumberedLineEmpty或FileNumberedLineEmpty 。 但是,如果只想將其打印出來,則獲得消息,然后將異常作為L(zhǎng)ineEmpty的實(shí)例來處理是絕對(duì)好的。 即使這樣,由于OO編程多態(tài)性,消息仍將包含人類可讀形式的額外信息。
吃的時(shí)候就是布丁的證明。 我們可以通過簡(jiǎn)單的測(cè)試運(yùn)行代碼。 測(cè)試代碼與以前的版本相同,唯一的例外是預(yù)期的異常類型為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我們可以對(duì)這個(gè)結(jié)果感到滿意,因?yàn)槲覀兞⒓纯吹綄?dǎo)致問題的文件是c.txt ,第四行是罪魁禍?zhǔn)住?另一方面,當(dāng)我們想看看引發(fā)異常的代碼時(shí),我們不會(huì)感到高興。 在將來的某個(gè)時(shí)候,我們可能不記得為什么一條線的長(zhǎng)度不能為零。 在這種情況下,我們想看一下代碼。 在那里,我們只會(huì)看到捕獲并重新拋出異常。 幸運(yùn)的是,這是有原因的,但是實(shí)際上直到到達(dá)LineWtfCounter.java:15的真正問題的代碼為止,這實(shí)際上是三個(gè)步驟。
有人會(huì)對(duì)捕獲和拋出異常的代碼感興趣嗎? 也許是的。 也許沒有。 在我們的案例中,我們決定將不會(huì)有人對(duì)該代碼感興趣,而不是處理一長(zhǎng)串列出有罪原因的異常鏈,而是將異常的堆棧跟蹤更改為引發(fā)異常的堆棧跟蹤
例外。
版本3,設(shè)置堆棧跟蹤
在此版本中,我們僅更改以下兩個(gè)異常的代碼: NumberedLineEmpty和FileNumberedLineEmpty 。 現(xiàn)在,他們不僅擴(kuò)展了彼此,又?jǐn)U展了另一個(gè)LineEmpty而且還將自己的堆棧跟蹤設(shè)置為引起異常的值。
這是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 ; } } 有一個(gè)公共的setStackTrace()方法,可用于設(shè)置異常的堆棧跟蹤。 有趣的是,此方法實(shí)際上是public ,不受保護(hù)。 該方法是public這一事實(shí)意味著可以從外部設(shè)置任何異常的堆棧跟蹤。 這樣做(可能)違反了封裝規(guī)則。
不過,它在那里,如果存在,那么我們可以使用它來將異常的堆棧跟蹤設(shè)置為與引起異常的堆棧跟蹤相同。
這些異常類中還有另一段有趣的代碼。 這是公共的fillInStackTrace()方法。 如果像上面那樣實(shí)現(xiàn)這一點(diǎn),那么我們可以節(jié)省異常在對(duì)象構(gòu)造過程中花費(fèi)的時(shí)間,以收集其自己的原始堆棧跟蹤信息,無論如何我們將其替換并丟棄。
當(dāng)我們創(chuàng)建一個(gè)新異常時(shí),構(gòu)造函數(shù)將調(diào)用本機(jī)方法來填充堆棧跟蹤。 如果查看類java.lang.Throwable的默認(rèn)構(gòu)造函數(shù),您會(huì)發(fā)現(xiàn)實(shí)際上這就是它的全部功能(Java 14 OpenJDK):
public Throwable() { fillInStackTrace(); }方法fillInStackTrace()不是本機(jī)的,但這是實(shí)際上調(diào)用完成工作的本機(jī)fillInStackTrace(int)方法的方法。 這是完成的過程:
public synchronized Throwable fillInStackTrace() { if (stackTrace != null || backtrace != null /* Out of protocol state */ ) { fillInStackTrace( 0 ); stackTrace = UNASSIGNED_STACK; } return this ; }它里面有一些“魔術(shù)”,它如何設(shè)置字段stackTrace但是到目前為止,這還不是很重要。 但是,請(qǐng)務(wù)必注意,方法fillInStackTrace()是public 。 這意味著它可以被覆蓋。 (為此, protected就足夠了,但public更是允許。)
我們還設(shè)置了引起異常,在這種情況下,它將具有相同的堆棧跟蹤。 運(yùn)行測(cè)試(類似于我們先前僅列出其中一項(xiàng)的測(cè)試),我們將打印出堆棧:
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毫不奇怪,我們有一個(gè)FileNumberedLineEmpty ,它的堆棧跟蹤從代碼行LineWtfCounter.java:15 ,不會(huì)引發(fā)該異常。 當(dāng)我們看到這一點(diǎn)時(shí),可能會(huì)有一些辯論:
- 當(dāng)我們覆蓋堆棧跟蹤時(shí),為什么我們需要在原始文件上附加引起異常的原因? (我們不。)
- 這是一個(gè)干凈的解決方案嗎? 堆棧跟蹤源自沒有引發(fā)該異常的行可能會(huì)造成混淆。
讓我們回答這些問題,是的,出于演示目的,它們是必需的。在實(shí)際的應(yīng)用程序中,每個(gè)程序員都可以決定是否要使用這樣的解決方案。
這是我們可以獲得的最佳解決方案嗎? 可能不是,因?yàn)檎缥宜兄Z的,我們擁有該應(yīng)用程序的第四版。
版本4,抑制異常
當(dāng)我們創(chuàng)建模擬FileReader我們非常樂觀。 我們假設(shè)只有一行的長(zhǎng)度為零。 如果有不止一條這樣的行怎么辦? 在這種情況下,應(yīng)用程序?qū)牡谝粋€(gè)停止。 用戶修復(fù)了以下錯(cuò)誤:要么在行中添加一些字符,以使該字符不再為空,要么完全刪除該錯(cuò)誤,以使該字符不再為行。 然后,用戶再次運(yùn)行該應(yīng)用程序以獲取異常中的第二個(gè)位置。 如果有很多這樣的行要糾正,那么此過程可能很麻煩。 您還可以想象,實(shí)際應(yīng)用程序中的代碼可能會(huì)運(yùn)行很長(zhǎng)時(shí)間,更不用說要花幾個(gè)小時(shí)了。 僅為了獲得問題的下一個(gè)位置而執(zhí)行該應(yīng)用程序就是在浪費(fèi)人力,浪費(fèi)CPU時(shí)鐘,能源,從而不必要地清潔產(chǎn)生氧氣的CO2。
我們可以做的是,更改應(yīng)用程序,以便在有空行的情況下繼續(xù)進(jìn)行處理,并且僅在處理完所有文件和所有行之后,它才會(huì)引發(fā)異常,列出所有在過程中發(fā)現(xiàn)并為空的行。 有兩種方法。 一種是創(chuàng)建一些數(shù)據(jù)結(jié)構(gòu)并將信息存儲(chǔ)在其中,然后在處理結(jié)束時(shí),應(yīng)用程序可以查看該數(shù)據(jù)結(jié)構(gòu),并在其中存在有關(guān)某些空行的任何信息時(shí)引發(fā)異常。 另一種是使用異常類提供的結(jié)構(gòu)來存儲(chǔ)信息。
好處是使用異常類提供的結(jié)構(gòu)是
- 結(jié)構(gòu)已經(jīng)在那里,不需要重新發(fā)明輪子,
- 它是由許多經(jīng)驗(yàn)豐富的開發(fā)人員精心設(shè)計(jì)的,并且已經(jīng)使用了數(shù)十年,可能是正確的結(jié)構(gòu),
- 該結(jié)構(gòu)的通用性足以容納其他類型的異常,不僅是我們當(dāng)前擁有的異常,而且數(shù)據(jù)結(jié)構(gòu)不需要任何更改。
讓我們討論最后一點(diǎn)。 稍后可能會(huì)發(fā)生的情況是,我們決定包含WTF所有資本的行也是例外的,應(yīng)該拋出異常。 在這種情況下,如果我們決定手工制作這些結(jié)構(gòu),則可能需要修改存儲(chǔ)這些錯(cuò)誤情況的數(shù)據(jù)結(jié)構(gòu)。 如果我們使用Throwable類的受抑制異常,則沒有其他事情要做。 有一個(gè)異常,我們將其捕獲(如您將在示例中很快看到的那樣),將其存儲(chǔ),然后將其作為抑制的異常附加到摘要異常的末尾。 當(dāng)該演示應(yīng)用程序極不可能擴(kuò)展時(shí),我們是否會(huì)考慮YAGNI? 是的,不是,通常沒有關(guān)系。 當(dāng)您花時(shí)間和精力過早開發(fā)某些東西時(shí),YAGNI通常是一個(gè)問題。 在開發(fā)中以及隨后的維護(hù)中,這是一筆額外的費(fèi)用。 當(dāng)我們只使用已經(jīng)存在的更簡(jiǎn)單的東西時(shí),不是YAGNI使用它。 它對(duì)我們使用的工具非常聰明并且知識(shí)淵博。
讓我們看一下修改后的FileReader ,這次它已經(jīng)在許多文件中返回許多空行:
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); } }現(xiàn)在,所有三個(gè)文件都包含空行。 我們不需要修改LineWtfCounter計(jì)數(shù)器。 空行時(shí),我們拋出異常。 在此級(jí)別上,沒有辦法抑制此異常。 我們無法在此處收集任何例外列表。 我們只關(guān)注可能為空的一行。
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; } }當(dāng)我們捕獲LineEmpty異常時(shí),我們將其存儲(chǔ)在局部變量exceptionCollector引用的聚合exceptionCollector 。 如果沒有exceptionCollector則在添加捕獲到的異常之前先創(chuàng)建一個(gè),以避免NPE。 在處理的最后,當(dāng)我們處理完所有行時(shí),我們可能會(huì)將許多異常添加到摘要異常exceptionCollector 。 如果存在,則將其拋出。
同樣, ProjectWftCounter收集由不同F(xiàn)ileWtfCounter實(shí)例引發(fā)的所有異常,并且在處理結(jié)束時(shí),它將引發(fā)摘要異常,如以下代碼行所示:
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; } }現(xiàn)在,我們已經(jīng)將所有有問題的行收集到一個(gè)巨大的異常結(jié)構(gòu)中,我們應(yīng)該得到一個(gè)堆棧跟蹤:
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這次,我沒有刪除任何線條以使您感覺到它在肩上的重量。 現(xiàn)在,您可能會(huì)開始考慮使用異常結(jié)構(gòu)而不是僅包含我們所需信息的整潔,苗條的專用數(shù)據(jù)結(jié)構(gòu)是否值得。 如果您開始這樣認(rèn)為, 那就停止它 。 不要這樣 問題(如果有的話)不是我們有太多信息。 問題是我們的表達(dá)方式。 為了克服它,解決方案不是將嬰兒洗澡水倒掉……多余的信息,而是以更具可讀性的方式表示出來。 如果應(yīng)用程序很少遇到許多空行,那么對(duì)堆棧跟蹤進(jìn)行讀取可能不會(huì)給用戶帶來難以承受的負(fù)擔(dān)。 如果這是一個(gè)經(jīng)常出現(xiàn)的問題,并且您希望對(duì)用戶(客戶,支付賬單的用戶)友好,那么,也許不錯(cuò)的異常結(jié)構(gòu)打印機(jī)是一個(gè)不錯(cuò)的解決方案。
我們?cè)陧?xiàng)目中實(shí)際上有一個(gè)適合您
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" )這樣,我們就結(jié)束了練習(xí)。 我們逐步完成了以下步驟:從v1簡(jiǎn)單地引發(fā)和捕獲異常,到v2設(shè)置導(dǎo)致異常的娃套風(fēng)格, v3更改嵌入異常的堆棧跟蹤,最后v4存儲(chǔ)我們?cè)谔幚磉^程中收集的所有抑制的異常。 您現(xiàn)在可以做的是下載項(xiàng)目,進(jìn)行操作,檢查堆棧跟蹤,修改代碼,等等。 或者繼續(xù)閱讀,我們有一些有關(guān)異常的額外信息,這些基本級(jí)教程很少討論這些異常,也值得閱讀最后的總結(jié)部分。
有關(guān)異常的其他注意事項(xiàng)
在本節(jié)中,我們將告訴您一些關(guān)于異常的基本Java教程中并不為人們所熟知的信息,而這些信息通常是缺失的。
JVM中沒有檢查異常的東西
除非方法聲明明確指出可能會(huì)發(fā)生這種情況,否則無法從Java方法中引發(fā)已檢查的異常。 有趣的是,JVM不了解檢查異常的概念。 這是Java編譯器處理的事情,但是當(dāng)代碼進(jìn)入JVM時(shí),不會(huì)對(duì)此進(jìn)行檢查。
Throwable (checked) <-- Exception (checked) <-- RuntimeException (unchecked) <-- Other Exceptions (checked) <-- Error (unchecked) 異常類的結(jié)構(gòu)如上所述。 異常的根類是Throwable 。 可以拋出作為類實(shí)例的任何對(duì)象,直接或間接擴(kuò)展Throwable類。 將檢查根類Throwable ,因此,如果從方法中拋出了它的實(shí)例,則必須對(duì)其進(jìn)行聲明。
如果任何類直接擴(kuò)展該類并從方法中拋出,則必須再次聲明它。 除非對(duì)象也是RuntimeException或Error的實(shí)例。 在這種情況下,不檢查異常或錯(cuò)誤,可以在不聲明throwing方法的情況下拋出該異常或錯(cuò)誤。
檢查異常的想法是有爭(zhēng)議的。 使用它有很多優(yōu)點(diǎn),但是有許多語(yǔ)言沒有它的概念。 這就是JVM不強(qiáng)制執(zhí)行檢查異常的聲明的原因。 如果這樣做的話,就不可能從不需要聲明的異常并且想要與Java異常互操作的語(yǔ)言中生成JVM代碼。 當(dāng)我們?cè)贘ava中使用流時(shí),檢查異常也會(huì)引起很多麻煩。
可以克服檢查的異常。 使用某種技巧創(chuàng)建的方法,或者僅使用Java以外的JVM語(yǔ)言創(chuàng)建的方法,即使該方法未聲明要拋出的異常,也可以拋出已檢查的異常。 hacky方式使用一種簡(jiǎn)單的static實(shí)用程序方法,如以下代碼片段所示:
package javax0.blog.demo.throwable.sneaky; public class SneakyThrower { public static <E extends Throwable> E throwSneaky(Throwable e) throws E { throw (E) e; } }當(dāng)代碼引發(fā)一個(gè)檢查異常時(shí),例如Exception然后將其傳遞給throwSneaky()將使編譯器傻瓜。 編譯器將查看靜態(tài)方法的聲明,并且無法決定是否檢查其拋出的Throwable 。 這樣,它將不需要在throwing方法中聲明異常。
此方法的使用非常簡(jiǎn)單,并通過以下單元測(cè)試代碼進(jìn)行了演示:
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()演示了正常拋出和偷偷摸摸的拋出之間的區(qū)別。
throwSneaky()方法從不返回,并且仍然具有聲明的返回值。 這樣做的原因是允許在方法doesNotReturn()可以看到的模式接近文本代碼的結(jié)尾。 我們知道方法throwSneaky()從不返回,但是編譯器不知道。 如果我們簡(jiǎn)單地調(diào)用它,則編譯器仍將在我們的方法中需要一些return語(yǔ)句。 在更復(fù)雜的代碼流中,它可能會(huì)抱怨未初始化的變量。 另一方面,如果我們?cè)诖a中“拋出”返回值,那么它將為編譯器提供有關(guān)執(zhí)行流程的提示。 實(shí)際不會(huì)在此級(jí)別上進(jìn)行實(shí)際投擲,但這無關(guān)緊要。
永遠(yuǎn)不要抓
當(dāng)我們捕獲異常時(shí),我們可以捕獲檢查異常, RuntimeException或任何Throwable 。 不過,也有其他的東西,是Throwable ,但都沒有異常,也不會(huì)檢查。 這些是錯(cuò)誤。
故事:
我進(jìn)行了很多技術(shù)面試,應(yīng)聘者會(huì)來回答我的問題。 我對(duì)此有很多保留和不好的感覺。 我不喜歡玩“上帝”。 另一方面,當(dāng)我遇到聰明的人時(shí),即使他們不適合給定的工作職位,我也會(huì)很享受。 我通常嘗試進(jìn)行面試,從中獲得的價(jià)值不僅是對(duì)候選人的評(píng)價(jià),而且是候選人可以從中了解Java,專業(yè)或自己的東西。 有一個(gè)可以使用循環(huán)解決的編碼任務(wù),但是它誘使沒有經(jīng)驗(yàn)的開發(fā)人員擁有遞歸的解決方案。 許多創(chuàng)建遞歸解決方案的開發(fā)人員意識(shí)到,對(duì)于某些類型的輸入?yún)?shù),代碼中沒有退出條件。 (除非是因?yàn)樗麄円月斆鞯姆绞竭@樣做。但是,當(dāng)他們有足夠的經(jīng)驗(yàn)時(shí),他們不會(huì)尋求遞歸解決方案而不是簡(jiǎn)單的循環(huán)。因此,當(dāng)它是遞歸解決方案時(shí),它們幾乎永遠(yuǎn)不會(huì)有退出條件。 )如果我們使用永不結(jié)束遞歸循環(huán)的輸入?yún)?shù)運(yùn)行該代碼,將會(huì)發(fā)生什么? 我們得到一個(gè)StackOverflowException 。 在面試的壓力和壓力下,他們中的許多人編寫了一些捕獲此異常的代碼。 這是有問題的。 這是一個(gè)陷阱!
為什么是陷阱? 因?yàn)榇a永遠(yuǎn)不會(huì)拋出StackOverflowException 。 JDK中沒有StackOverflowException這樣的東西。 它是StackOverflowError 。 這也不例外,規(guī)則是
您的代碼絕不能出錯(cuò)
StackOverflowError (并非例外)擴(kuò)展了VirtualMachineError類,該類在JavaDoc中說:
拋出以表明Java虛擬機(jī)已損壞
發(fā)生故障時(shí),您可以將其粘合在一起,進(jìn)行修補(bǔ),修復(fù),但是您永遠(yuǎn)都不能使其斷裂。 如果捕獲的Throwable也是Error的實(shí)例,則在catch部分執(zhí)行的代碼將在損壞的VM中運(yùn)行。 那里會(huì)發(fā)生什么? 任何事情以及執(zhí)行的繼續(xù)可能都不可靠。
永遠(yuǎn)不要發(fā)現(xiàn)Error !
摘要和總結(jié)
在本文中,我們討論了異常,特別是:
- 如何通過在可用時(shí)添加信息來引發(fā)有意義的異常,
- 在setTrackTrace()的情況下如何使用setTrackTrace()替換異常的堆棧跟蹤,
- 當(dāng)您的應(yīng)用程序可以多次拋出異常時(shí),如何使用addSuppressed()收集異常我們還討論了一些有趣的知識(shí),關(guān)于JVM如何不了解已檢查的異常以及為什么永遠(yuǎn)不應(yīng)該捕獲Error 。
不要只是在異常發(fā)生時(shí)(重新)拋出異常。 考慮一下它們?yōu)槭裁窗l(fā)生以及如何發(fā)生,并適當(dāng)?shù)靥幚硭鼈儭?
使用本文中的信息使您的代碼與眾不同😉
(代碼和文章由Mihaly Verhas進(jìn)行了審核和校對(duì)。他還寫了外賣部分,包括最后一篇
句子。)
翻譯自: https://www.javacodegeeks.com/2020/05/all-you-wanted-to-know-about-throwable.html
throwable
總結(jié)
以上是生活随笔為你收集整理的throwable_您想了解的所有Throwable的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ui自动化测试测试报告_您需要了解的有关
- 下一篇: java 创建uri_使用UriBuil