1. 程式人生 > >Java常見重構技巧 - 去除不必要的!=null判斷空的5種方式,很少有人知道後兩種

Java常見重構技巧 - 去除不必要的!=null判斷空的5種方式,很少有人知道後兩種

# 常見重構技巧 - 去除不必要的!= > 專案中會存在大量判空程式碼,多麼醜陋繁冗!如何避免這種情況?我們是否濫用了判空呢?@pdai - [常見重構技巧 - 去除不必要的!=](#%e5%b8%b8%e8%a7%81%e9%87%8d%e6%9e%84%e6%8a%80%e5%b7%a7---%e5%8e%bb%e9%99%a4%e4%b8%8d%e5%bf%85%e8%a6%81%e7%9a%84) - [場景一:null無意義之常規判斷空](#%e5%9c%ba%e6%99%af%e4%b8%80null%e6%97%a0%e6%84%8f%e4%b9%89%e4%b9%8b%e5%b8%b8%e8%a7%84%e5%88%a4%e6%96%ad%e7%a9%ba) - [場景二:null無意義之使用斷言Assert](#%e5%9c%ba%e6%99%af%e4%ba%8cnull%e6%97%a0%e6%84%8f%e4%b9%89%e4%b9%8b%e4%bd%bf%e7%94%a8%e6%96%ad%e8%a8%80assert) - [場景三:寫util類是否都需要逐級判斷空](#%e5%9c%ba%e6%99%af%e4%b8%89%e5%86%99util%e7%b1%bb%e6%98%af%e5%90%a6%e9%83%bd%e9%9c%80%e8%a6%81%e9%80%90%e7%ba%a7%e5%88%a4%e6%96%ad%e7%a9%ba) - [場景四:讓null變的有意義](#%e5%9c%ba%e6%99%af%e5%9b%9b%e8%ae%a9null%e5%8f%98%e7%9a%84%e6%9c%89%e6%84%8f%e4%b9%89) - [場景五:Java8中使用Optional](#%e5%9c%ba%e6%99%af%e4%ba%94java8%e4%b8%ad%e4%bd%bf%e7%94%a8optional) ## 場景一:null無意義之常規判斷空 + 通常是這樣的 ```java private void xxxMethod(String key){ if(key!=null&&!"".equals(key)){ // do something } } ``` + 初步的,使用Apache Commons,Guvava, Hutool等StringUtils ```java private void xxxMethod(String key){ if(StringUtils.isNotEmpty(key)){ // do something } } ``` ## 場景二:null無意義之使用斷言Assert + 考慮用Assert斷言 ```java private void xxxMethod(String key){ Assert.notNull(key); // do something } ``` ## 場景三:寫util類是否都需要逐級判斷空 > 逐級判斷空,還是丟擲自定義異常,還是不處理?It Depends... 隨手翻了下,[hutool IdcardUtil](https://gitee.com/loolly/hutool/blob/v5-dev/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java) 顯然是交給呼叫者判斷的。 ```java /** * 是否有效身份證號 * * @param idCard 身份證號,支援18位、15位和港澳臺的10位 * @return 是否有效 */ public static boolean isValidCard(String idCard) { idCard = idCard.trim();// 這裡idCard沒判斷空 int length = idCard.length(); switch (length) { case 18:// 18位身份證 return isValidCard18(idCard); case 15:// 15位身份證 return isValidCard15(idCard); case 10: {// 10位身份證,港澳臺地區 String[] cardVal = isValidCard10(idCard); return null != cardVal && "true".equals(cardVal[2]); } default: return false; } } ``` + 再比如 Apache Common IO中, 並沒判斷空 ```java /** * Copy bytes from a byte[]
to an OutputStream. * @param input the byte array to read from * @param output the OutputStream to write to * @throws IOException In case of an I/O problem */ public static void copy(final byte[] input, final OutputStream output) throws IOException { output.write(input); } ``` ## 場景四:讓null變的有意義 >
返回一個空物件(而非null物件),比如NO_ACTION是特殊的Action,那麼我們就定義一個ACTION。下面舉個“栗子”,假設有如下程式碼 ```java public interface Action { void doSomething();} public interface Parser { Action findAction(String userInput); } ``` 其中,Parse有一個介面FindAction,這個介面會依據使用者的輸入,找到並執行對應的動作。假如使用者輸入不對,可能就找不到對應的動作(Action),因此findAction就會返回null,接下來action呼叫doSomething方法時,就會出現空指標。 解決這個問題的一個方式,就是使用Null Object pattern(空物件模式) >
NullObject模式首次發表在“ 程式設計模式語言 ”系列叢書中。一般的,在面嚮物件語言中,對物件的呼叫前需要使用判空檢查,來判斷這些物件是否為空,因為在空引用上無法呼叫所需方法。 ![](https://www.pdai.tech/_images/develop/refactor/dev-refactor-notnull-1.png) 我們來改造一下 類定義如下,這樣定義findAction方法後,確保無論使用者輸入什麼,都不會返回null物件: ```java public class MyParser implements Parser { private static Action NO_ACTION = new Action() { public void doSomething() { /* do nothing */ } }; public Action findAction(String userInput) { // ... if ( /* we can't find any actions */ ) { return NO_ACTION; } } } ``` 對比下面兩份呼叫例項 1.冗餘: 每獲取一個物件,就判一次空 ```java Parser parser = ParserFactory.getParser(); if (parser == null) { // now what? // this would be an example of where null isn't (or shouldn't be) a valid response } Action action = parser.findAction(someInput); if (action == null) { // do nothing} else { action.doSomething(); } ``` 2.精簡 ```java ParserFactory.getParser().findAction(someInput).doSomething(); ``` 因為無論什麼情況,都不會返回空物件,因此通過findAction拿到action後,可以放心地呼叫action的方法。 順便再提下一個外掛: >.NR Null Object外掛 NR Null Object是一款適用於Android Studio、IntelliJ IDEA、PhpStorm、WebStorm、PyCharm、RubyMine、AppCode、CLion、GoLand、DataGrip等IDEA的Intellij外掛。其可以根據現有物件,便捷快速生成其空物件模式需要的組成成分,其包含功能如下: + 分析所選類可宣告為介面的方法; + 抽象出公有介面; + 建立空物件,自動實現公有介面; + 對部分函式進行可為空宣告; + 可追加函式進行再次生成; + 自動的函式命名規範 ## 場景五:Java8中使用Optional 假設我們有一個像這樣的類層次結構: ```java class Outer { Nested nested; Nested getNested() { return nested; } } class Nested { Inner inner; Inner getInner() { return inner; } } class Inner { String foo; String getFoo() { return foo; } } ``` 解決這種結構的深層巢狀路徑是有點麻煩的。我們必須編寫一堆 null 檢查來確保不會導致一個 NullPointerException: ```java Outer outer = new Outer(); if (outer != null && outer.nested != null && outer.nested.inner != null) { System.out.println(outer.nested.inner.foo); } ``` 我們可以通過利用 Java 8 的 Optional 型別來擺脫所有這些 null 檢查。map 方法接收一個 Function 型別的 lambda 表示式,並自動將每個 function 的結果包裝成一個 Optional 物件。這使我們能夠在一行中進行多個 map 操作。Null 檢查是在底層自動處理的。 ```java Optional.of(new Outer()) .map(Outer::getNested) .map(Nested::getInner) .map(Inner::getFoo) .ifPresent(System.out::println); ``` 還有一種實現相同作用的方式就是通過利用一個 supplier 函式來解決巢狀路徑的問題: ```java Outer obj = new Outer(); resolve(() -> obj.getNested().getInner().getFoo()); .ifPresent(System.out::println); ``` 呼叫 obj.getNested().getInner().getFoo()) 可能會丟擲一個 NullPointerException 異常。在這種情況下,該異常將會被捕獲,而該方法會返回 Optional.empty()。 ```java public static Optional resolve(Supplier resolver) { try { T result = resolver.get(); return Optional.ofNullable(result); } catch (NullPointerException e) { return Optional.empty(); } } ``` 請記住,這兩個解決方案可能沒有傳統 null 檢查那麼高的效能。不過在大多數情況下不會有太大問題。 + 更多Optional,可以看這篇: [Java 8 - Optional類](https://www.pdai.tech/md/java/java8/java8-optional.html) + Optional類的意義 + Optional類有哪些常用的方法 + Optional舉例貫穿所有知識點 + 多重類巢狀Null值判斷 ## 更多 更多文章請參考 [Java 全棧知識體系](https://www.pdai.tech)