Java常見重構技巧 - 去除不必要的!=
項目中會存在大量判空代碼,多麼醜陋繁冗!如何避免這種情況?我們是否濫用了判空呢?@pdai
- 常見重構技巧 - 去除不必要的!=
- 場景一:null無意義之正常判斷空
- 場景二:null無意義之使用斷言Assert
- 場景三:寫util類是否都需要逐級判斷空
- 場景四:讓null變的有意義
- 場景五:Java8中使用Optional
場景一: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.精簡
因為無論什麼情況,都不會傳回空對象,是以通過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 全棧知識體系 400+文章帶你構築Java開發體系