async 打包异常_重新打包流中的异常
async 打包異常
Java 8已有兩年歷史,但是仍然存在社區尚未為其開發好的解決方案庫的用例,甚至邊緣用例。 如何處理流管道中的檢查異常就是這樣一個問題。 Stream操作接受的功能接口不允許實現拋出已檢查的異常,但是我們可能要調用許多方法。 顯然,這里存在一種緊張關系,許多開發人員都曾遇到過這種緊張關系。
我想在簡短的系列文章中探討這個主題:
重新打包流中的異常我的主要目標是提出各種解決方案,并且在理想情況下,建立使討論變得更容易的通用術語。 我還將對我的建議進行評論,并添加自己的評估意見-盡管這是次要的,但我希望它不會偏離主要目標:將想法付諸實踐。
第一篇文章將研究重新打包異常,以便編譯器停止抱怨。
設置場景
基本場景是流的每個頻繁用戶都遇到的一種或多種形式:您想在流的中間操作之一中使用的方法拋出一個已檢查的異常。
在本文中,我將假定您正在嘗試將字符串流解析為用戶流:
Stream<User> parse(Stream<String> strings) {return strings.map(User::parse); }(如果您不打算將流作為參數或返回值,則假定整個流管道都在方法的范圍內。以下方法適用于這兩種方式,但是如果您在處理流上使用整個流,則某些評估會有所不同。點。)
不幸的是, User::parse可以拋出ParseException :
public class User {public static User parse(String userString) throws ParseException {// ...}}這導致編譯器抱怨方法參考User::parse “未處理的異常:java.text.ParseException” 。 現在要做什么?
在我們研究此問題的解決方案之前,我想指出一點:我不認為Stream API與檢查異常的不兼容性是可以通過其他設計克服的。 在某個時候,我可能會寫一個更長的帖子來解釋這一點,但是簡短的版本是這樣的:如果功能接口方法可以拋出檢查的異常,那么將沒有一種愉快的方式將其與流的惰性結合起來,因為它將是終端操作最終拋出該異常。
但是我們可以充分利用可以引發異常的函數,因此讓我們在介紹該接口時對其進行介紹:
@FunctionalInterface interface CheckedFunction<T, R, EX extends Exception> {R apply(T element) throws EX;} 這使我們可以將User::parse分配給CheckedFunction<String , User, ParseException> 。 請注意,異常的類型是通用的,稍后將派上用場。
重新打包流中的異常
那么,您真的必須處理例外情況嗎? 不知道,您能不能解決問題? 令人驚訝的答案是“是的,您可以。” 是否應該拭目以待……
包裝未檢查的異常
給定一個引發檢查異常的函數,將其轉換為引發未檢查異常的函數非常容易:
Stream<User> parse(Stream<String> strings) {return strings.map(uncheckException(User::parse)) }<T, R> Function<T, R> uncheckException(CheckedFunction<T, R, Exception> function) {return element -> {try {return function.apply(element);} catch (Exception ex) {// thanks to Christian Schneider for pointing out// that unchecked exceptions need not be wrapped againif (ex instanceof RuntimeException)throw (RuntimeException) ex;elsethrow new RuntimeException(ex);}}; }這實際上還不錯。 而且,無論如何,如果您更喜歡未檢查的異常,那么這將更加誘人。 另一方面,如果您重視檢查的異常(對于您期望的事情可能會出錯,例如錯誤的輸入)與未檢查的異常(對于實現錯誤)之間的區別,那么這將使您不寒而栗。
在任何情況下,流的最終使用者都必須意識到可能會引發異常,這時需要與測試或文檔進行通信,這兩者都比編譯器更容易忽略。 感覺就像在小河里藏了一顆炸彈。
最后,請注意,這會在第一個錯誤發生時立即中止流-可能會或可能不會發生的事情。 如果該方法返回一個流而不是使用它,則很難確定是否可行,因為不同的調用者可能有不同的要求。
偷偷摸摸的異常
解決整個問題的另一種方法是“偷偷地”拋出異常。 該技術使用泛型來混淆編譯器,并使用@SuppressWarnings使其剩余的投訴靜音。
Stream<User> parse(Stream<String> strings) {return strings.map(hideException(User::parse)); }<T, R> Function<T, R> hideException(CheckedFunction<T, R, Exception> function) {return element -> {try {return function.apply(element);} catch (Exception ex) {return sneakyThrow(ex);}}; }@SuppressWarnings("unchecked") <E extends Throwable, T> T sneakyThrow(Throwable t) throws E {throw (E) t; }嗯,什么? 如所承諾的, sneakyThrow方法使用泛型來欺騙編譯器以拋出未經檢查的異常而不聲明它。 然后hideException使用它來捕獲CheckedFunction可能拋出的任何異常并CheckedFunction將其重新拋出。 (如果您使用的是Lombok,請查看其@SneakyThrows批注 。)
我認為這是非常冒險的舉動。 一方面,它仍然在小河中隱藏著一顆炸彈。 但是,它進一步發展了,并使炸彈難以妥善化解。 您是否曾經嘗試捕獲未使用throws子句聲明的檢查異常?
try {userStrings.stream().map(hideException(User::parse));.forEach(System.out::println); // compile error because ParseException // is not declared as being thrown } catch (ParseException ex) {// handle exception }無法工作,因為編譯器在沒有方法實際拋出ParseException的假設下運行。 相反,您必須捕獲Exception ,過濾掉ParseException并重新拋出其他所有內容。
哇,真爛!
不幸的是,這種技術在StackOverflow答案中得到了體現,在尋找Java流異常處理時,它在Google上的排名非常高。 公平地說,答案包含免責聲明,但恐怕它可能會經常被忽略:
不用說,應該小心處理,項目中的每個人都必須意識到,未經聲明的異常可能會出現在經過檢查的異常中。
但是,正如我們已經看到的那樣,沒有很好的方法來聲明/捕獲這樣的異常,因此我要說的是:
這是一個不錯的實驗,但從未真正做到! 如果確實要拋出,請包裝運行時異常。
電梯例外
偷偷摸摸的問題是,這使流的消費者感到驚訝, 并且即使他們克服了這種驚訝,也很難處理該異常。 對于后者,至少有一個出路。 考慮以下功能:
<T, R, EX extends Exception> Function<T, R> liftException(CheckedFunction<T, R, EX> function) throws EX {return hideException(function); }它與hideException完全相同, 但是聲明它拋出EX。 為什么會有幫助? 因為可以通過這種方式使編譯器理解可能會拋出檢查異常:
Stream<User> parse(Stream<String> strings) {return strings// does not compile because `liftException`// throws ParseException but it is unhandled.map(liftException(User::parse)); }問題是, liftException的主體非常清楚地表明,它當然不會引發異常。 因此,在這樣的示例中,我們僅看到管道的一部分,可以說使情況更加混亂。 現在,解析調用者可能會將其放入try-catch塊中,期望能夠很好地處理異常(如果不要對它太認真地考慮),然后當終端操作拋出該異常時仍會感到驚訝(記住它被sneakyThrow )隱藏了。
但是,如果您是從不返回流的人, liftException非常有用。 有了它,您的流管道中的一些調用就聲明拋出一個已檢查的異常,因此您可以將其全部放入try-catch塊中:
try {userStrings.stream().map(liftException(User::parse));.forEach(System.out::println); } catch (ParseException ex) {// handle exception }另外,包含管道的方法可以聲明拋出異常:
List<User> parse(List<String> userStrings) throws ParseException {return userStrings.stream().map(liftException(User::parse));.collect(toList()); }但是正如我之前所說,我認為只有在您永不返回流的情況下,此方法才有效。 因為如果這樣做(即使只是偶爾這樣做),則存在風險,即您或您的同事在重構期間可能會將管道拆開,從而使炸彈處于未聲明的檢查異常的狀態,并隱藏在流中。
Sebastian Millies指出了另一個缺點,即到目前為止使用的接口和方法僅允許一個例外。 一旦一種方法聲明了多個檢查異常,事情就會成問題。 要么讓Java派生一個公共的超類型(可能是Exception ), liftException為一個以上的異常聲明其他CheckedFunction接口和liftException方法。 兩者都不是很好的選擇。
給定拋出異常的方法,如果需要立即拋出異常,我向您展示了兩種不同的方式在流中使用它們:
- 將檢查的異常包裝在運行時異常中
- 偷偷地拋出已檢查的異常,以便編譯器無法識別被拋出的異常
- 仍然偷偷摸摸地拋出,但是讓utitility函數聲明異常,以便編譯器至少知道它被拋出了
請注意,所有這些方法都意味著流管線將在那里停止處理,除非產生副作用,否則不會產生任何結果。 我發現經常是不是我想做的事,但(因為我不喜歡返回物流)。 下一篇文章通過研究如何在不中斷管道的情況下當場處理異常來解決此問題。
翻譯自: https://www.javacodegeeks.com/2017/02/repackaging-exceptions-streams.html
async 打包異常
總結
以上是生活随笔為你收集整理的async 打包异常_重新打包流中的异常的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 创建文件夹Linux命令(创建文件夹li
- 下一篇: 队列和消息队列_消息队列概述[幻灯片]