在寫代碼的時候, Android Studio 經常會提醒我們可以使用這個方法來進行參數(shù)非空檢查, 這個方法的源碼也非常簡單, 如下所示:
/**
* Checks that the specified object reference is not {@code null}. This
* method is designed primarily for doing parameter validation in methods
* and constructors, as demonstrated below:
* <blockquote><pre>
* public Foo(Bar bar) {
* this.bar = Objects.requireNonNull(bar);
* }
* </pre></blockquote>
*
* @param obj the object reference to check for nullity
* @param <T> the type of the reference
* @return {@code obj} if not {@code null}
* @throws NullPointerException if {@code obj} is {@code null}
*/
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
這個方法是 Objects
類的一個靜態(tài)方法, Objects
類是一個 Java 靜態(tài)類, 里面包含了很多 Java 工具方法, 其方法都是靜態(tài)方法, 其類的說明文檔如下:
/**
* This class consists of {@code static} utility methods for operating
* on objects. These utilities include {@code null}-safe or {@code
* null}-tolerant methods for computing the hash code of an object,
* returning a string for an object, and comparing two objects.
*
* @since 1.7
*/
public final class Objects {
...
}
可以看出, 這個類還包括了很多關于類操作的使用工具方法, 例如比較兩個類是否相等, 計算類的 Hash Code 等方法, 這個類以后有機會進行學習和介紹.
回到 requireNonNull()
這個方法, 其源碼實現(xiàn)非常簡單, 只是進行了一個簡單的判斷, 如果所要判斷的元素為 null
, 則返回空指針異常 NullPointerException
, 否則直接返回對應的對象.
這看上去好像是一個多余的操作, 因為如果我們試圖去調用一個空對象的方法, 也會拋出 NullPointerException
運行時異常, 那么我們?yōu)槭裁匆啻艘慌e進行這樣的一次檢查呢? 這一問題在 StackOverflow 上有人進行了解答 Why should one use Objects.requireNonNull?.
看了他們的回答, 總結為以下幾點:
首先, 從這個方法的名稱可以看出, 這個方法使用的場景是, 我們使用一個對象的方法時, 正常的運行狀態(tài)應該能保證這個對象的引用非空, 如果這個對象為空了, 那一定是其他某個地方出錯了, 所以我們應該拋出一個異常, 我們不應該在這里處理這個非空異常.
其次, 這里涉及到一個很重要的編程思想, 就是 Fail-fast 思想, 翻譯過來就是, 讓錯誤盡可能早的出現(xiàn), 不要等到我們很多工作執(zhí)行到一半之后才拋出異常, 這樣很可能使得一部分變量處于異常狀態(tài), 出現(xiàn)更多的錯誤. 這也是 requireNonNull
這個方法的設計思想, 讓錯誤盡早出現(xiàn). 使用這個方法, 我們明確的拋出異常, 發(fā)生錯誤時, 我們立刻拋出異常.
StackOverflow 中的一個回答舉了一個具體的例子來回答這個問題, 例如有下面這樣一個類:
public class Dictionary {
private final List<String> words;
private final LookupService lookupService;
public Dictionary(List<String> words) {
this.words = this.words;
this.lookupService = new LookupService(words);
}
public boolean isFirstElement(String userData) {
return lookupService.isFirstElement(userData);
}
}
public class LookupService {
List<String> words;
public LookupService(List<String> words) {
this.words = words;
}
public boolean isFirstElement(String userData) {
return words.get(0).contains(userData);
}
}
這里, 兩個類是包含的關系, 傳入的 List<String>
參數(shù)沒有做非空檢查. 如果我們一不小心在 Dictionary
的構造方法中傳入了 null
, 如下所示:
Dictionary dictionary = new Dictionary(null);
// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");
我們在構造時沒有任何異常, 但是當我們調用方法時, 會拋出 NPE:
Exception in thread "main" java.lang.NullPointerException
at LookupService.isFirstElement(LookupService.java:5)
at Dictionary.isFirstElement(Dictionary.java:15)
at Dictionary.main(Dictionary.java:22)
JVM 告訴我們, 在執(zhí)行 return words.get(0).contains(userData)
這條語句時, 發(fā)生了異常, 但是這個異常非常不明確, 從報錯信息來看, 有多種可能會導致這個異常發(fā)生, 是因為 words
為空, 還是 words.get(0)
為空? 或者兩者都為空? 這都是不明確的. 同時, 我們也無法確定是在這兩個類的哪個環(huán)節(jié)出了錯, 這些都是不明確的, 給我們程序 debug 造成了很大的困難.
然而, 當我們使用如下方式實現(xiàn):
public Dictionary(List<String> words) {
this.words = Objects.requireNonNull(words);
this.lookupService = new LookupService(words);
}
按照這種實現(xiàn)方式, 在我們執(zhí)行構造方法時, 就會明確拋出錯誤.
// exception thrown early : in the constructor
Dictionary dictionary = new Dictionary(null);
// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at com.Dictionary.(Dictionary.java:15)
at com.Dictionary.main(Dictionary.java:24)
這樣我們進行 debug 時就明確很多, 少走很多彎路.
除此之外, 這個方法的作用也是一個明確和不明確的區(qū)別, 使用這個方法表示我們明確進行了這個判斷, 其實與我們自己使用 if-else
進行判斷是一樣的, 只是這個工具類簡化了這樣的操作, 讓我們的代碼看上去更加簡潔, 可讀性更強.
此外, requireNonNull
方法有一個重載方法, 可以提供一個報錯信息, 以供我們 debug 的時候顯示. 我們使用這個引用的時候, 應當保證非空, 如果不然, 會拋出異常告訴我們其他地方出錯了, 這里出現(xiàn)了空指針異常. 這個方法重載的實現(xiàn)如下:
/**
* Checks that the specified object reference is not {@code null} and
* throws a customized {@link NullPointerException} if it is. This method
* is designed primarily for doing parameter validation in methods and
* constructors with multiple parameters, as demonstrated below:
* <blockquote><pre>
* public Foo(Bar bar, Baz baz) {
* this.bar = Objects.requireNonNull(bar, "bar must not be null");
* this.baz = Objects.requireNonNull(baz, "baz must not be null");
* }
* </pre></blockquote>
*
* @param obj the object reference to check for nullity
* @param message detail message to be used in the event that a {@code
* NullPointerException} is thrown
* @param <T> the type of the reference
* @return {@code obj} if not {@code null}
* @throws NullPointerException if {@code obj} is {@code null}
*/
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
例如, 我們在 Android 中可以按照如下方式使用:
String username = Objects.requireNonNull(textInputLayoutUsername.getEditText(), "TextInputLayout must have an EditText as child").getText().toString();
這是一個從 TextInpuLayout
獲取用戶輸入內容的方法, 通常使用 TextInputLayout
包裹一個 EditText
來接收用戶輸入, 因此我們需要通過 TextInputLayout
的 getEditText()
方法來獲取對應的 EditText
, 如果我們布局有問題, 則該方法可能返回 null
, 因此我們可以通過上述方法, 拋出一個明確異常, 如果運行時出現(xiàn)問題, 我們也可以很快知道是因為我們 TextInputLayout
無法獲取 EditText
而出錯的.