差異處
這裏顯示兩個版本的差異處。
Both sides previous revision 前次修改 下次修改 | 前次修改 | ||
java:java8:exception:observerexceptionhandlingoflambda [2018/01/21 23:10] tony |
java:java8:exception:observerexceptionhandlingoflambda [2023/06/25 09:48] (目前版本) |
||
---|---|---|---|
行 1: | 行 1: | ||
- | ====== Observer使用Lambda寫法進行通知的例外處理 (Working) ====== | + | ====== Observer使用Lambda寫法對通知的例外處理 ====== |
+ | ===== Introduction ===== | ||
+ | 這是一段常見的Observer寫法的程式碼,FileWatcher是Subject負責確認檔案是否修改,而IFileChangedListener是Observer接收檔案改變通知;當檔案改變時,FileWatcher會執行doOnChange通知各個IFileChangedListener: | ||
+ | <file java FileWatcher.java> | ||
+ | public class FileWatcher extends FileWatchdog { | ||
+ | private List<IFileChangedListener> listeners = new ArrayList<>(); | ||
+ | static private Logger logger = LoggerFactory.getLogger(FileWatcher.class); | ||
+ | |||
+ | public FileWatcher(String filename) { | ||
+ | super(filename); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void doOnChange() { | ||
+ | try { | ||
+ | if( listeners == null || listeners.isEmpty() ) | ||
+ | return; | ||
+ | |||
+ | String newContent = new String(Files.readAllBytes(Paths.get(super.filename))); | ||
+ | listeners.forEach(listener->listener.update(newContent)); | ||
+ | } catch( IOException e ){ | ||
+ | throw new RuntimeException(e); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public void addListener(IFileChangedListener listener){ | ||
+ | listeners.add(listener); | ||
+ | } | ||
+ | } | ||
+ | </file> | ||
+ | 假設我有10個listener,當第三個listener執行update時發生了例外,工作是不是就會停止了呢? 答案是肯定的。本篇主要分享可能的例外處理方式。 | ||
+ | ===== Handle Exception With A Block ===== | ||
+ | 最直接的方法就是把例外處理弄成一個區塊: | ||
+ | <code java> | ||
+ | listeners.forEach(listener->{ | ||
+ | try { | ||
+ | listener.update(newContent); | ||
+ | } catch( Exception e ) { | ||
+ | logger.warn("Update file info failed.", e); | ||
+ | } | ||
+ | }); | ||
+ | </code> | ||
+ | 但隨著不同例外處理需求或流程,會變得難以閱讀而喪失原本Lambda的美意。 | ||
+ | ===== Extract Exception Handling As A Method ===== | ||
+ | 以Lambda方式實作時,將處理抽成method以增加可讀性是常使用的做法: | ||
+ | <code java> | ||
+ | listeners.forEach(listener->updateEx(listener, newContent)); | ||
+ | |||
+ | private void updateEx(IFileChangedListener listener, String newContent){ | ||
+ | try { | ||
+ | listener.update(newContent); | ||
+ | } catch( Exception e ) { | ||
+ | logger.warn("Update file info failed.", e); | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | 接下來就是考慮重複使用的議題。 | ||
+ | ===== Wrapper The Exception Handling ===== | ||
+ | 為了重複使用,我們可以將處理方式透過Wrapper方式實作並集中於Utility類別: | ||
+ | <code java> | ||
+ | public class Errors { | ||
+ | static private Logger logger = LoggerFactory.getLogger(Errors.class); | ||
+ | public static <T> Consumer<T> logException(Consumer<T> operation){ | ||
+ | return i -> { | ||
+ | try { | ||
+ | operation.accept(i); | ||
+ | } catch (Exception e) { | ||
+ | logger.warn("", e); | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | listeners.forEach(Errors.logException(i->i.update(newContent))); | ||
+ | </code> | ||
+ | 上面這種寫法只能針對unchecked exception,假如是checked exception,可以透過Functional Interface做自己的Consumer: | ||
+ | <code java> | ||
+ | @FunctionalInterface | ||
+ | public interface ThrowingConsumer<T, E extends Exception> { | ||
+ | void accept(T t) throws E; | ||
+ | } | ||
+ | |||
+ | public static <T> Consumer<T> logException(ThrowingConsumer<T, Exception> operation){ | ||
+ | return i -> { | ||
+ | try { | ||
+ | operation.accept(i); | ||
+ | } catch (Exception e) { | ||
+ | logger.warn("", e); | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | </code> | ||
+ | ===== Apply durian - Error class ===== | ||
+ | 假如不介意相依於第三方函式庫,可以參考[[https://github.com/diffplug/durian|durian]]。它針對lambda的functional interface提供了log、rethrow或者自訂義等例外處理方式,這部分之後我有時間會特別介紹。 | ||
+ | ===== Reference ===== | ||
+ | * [[http://www.baeldung.com/java-lambda-exceptions|Exceptions in Java 8 Lambda Expressions]] | ||
+ | * [[https://github.com/diffplug/durian|Github - durian]] | ||
+ | |||
+ | ====== ====== | ||
+ | ---- | ||
+ | \\ | ||
+ | ~~DISQUS~~ |