常見重構技巧 - 去除不必要的!=
專案中會存在大量判空程式碼,多麼醜陋繁冗!如何避免這種情況?我們是否濫用了判空呢?@pdai
場景一:null無意義之常規判斷空
- 通常是這樣的
private void xxxMethod(String key){
if(key!=null&&!"".equals(key)){
// do something
}
}
- 初步的,使用Apache Commons,Guvava, Hutool等StringUtils
private void xxxMethod(String key){
if(StringUtils.isNotEmpty(key)){
// do something
}
}
場景二:null無意義之使用斷言Assert
- 考慮用Assert斷言
private void xxxMethod(String key){
Assert.notNull(key);
// do something
}
場景三:寫util類是否都需要逐級判斷空
逐級判斷空,還是丟擲自定義異常,還是不處理?It Depends...
隨手翻了下,hutool IdcardUtil 顯然是交給呼叫者判斷的。
/**
* 是否有效身份證號
*
* @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中, 並沒判斷空
/**
* Copy bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
* @param input the byte array to read from
* @param output the <code>OutputStream</code> 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。下面舉個“栗子”,假設有如下程式碼
public interface Action {
void doSomething();}
public interface Parser {
Action findAction(String userInput);
}
其中,Parse有一個介面FindAction,這個介面會依據使用者的輸入,找到並執行對應的動作。假如使用者輸入不對,可能就找不到對應的動作(Action),因此findAction就會返回null,接下來action呼叫doSomething方法時,就會出現空指標。
解決這個問題的一個方式,就是使用Null Object pattern(空物件模式)
NullObject模式首次發表在“ 程式設計模式語言 ”系列叢書中。一般的,在面嚮物件語言中,對物件的呼叫前需要使用判空檢查,來判斷這些物件是否為空,因為在空引用上無法呼叫所需方法。
我們來改造一下
類定義如下,這樣定義findAction方法後,確保無論使用者輸入什麼,都不會返回null物件:
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.冗餘: 每獲取一個物件,就判一次空
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.精簡
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
假設我們有一個像這樣的類層次結構:
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:
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 檢查是在底層自動處理的。
Optional.of(new Outer())
.map(Outer::getNested)
.map(Nested::getInner)
.map(Inner::getFoo)
.ifPresent(System.out::println);
還有一種實現相同作用的方式就是通過利用一個 supplier 函式來解決巢狀路徑的問題:
Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo());
.ifPresent(System.out::println);
呼叫 obj.getNested().getInner().getFoo()) 可能會丟擲一個 NullPointerException 異常。在這種情況下,該異常將會被捕獲,而該方法會返回 Optional.empty()。
public static <T> Optional<T> resolve(Supplier<T> resolver) {
try {
T result = resolver.get();
return Optional.ofNullable(result);
}
catch (NullPointerException e) {
return Optional.empty();
}
}
請記住,這兩個解決方案可能沒有傳統 null 檢查那麼高的效能。不過在大多數情況下不會有太大問題。
- 更多Optional,可以看這篇: Java 8 - Optional類
- Optional類的意義
- Optional類有哪些常用的方法
- Optional舉例貫穿所有知識點
- 多重類巢狀Null值判斷
更多
更多文章請參考 Java 全棧知識體系