點關注,不迷路;持續更新Java架構相關技術及資訊熱文!!!
一般,在Java代碼中處理null變量、引用和集合很棘手。它們不只難以識別,並且處理起來也很複雜。事實上,在編譯時沒法識別處理null的任何錯誤,會致使運行時NullPointerException。在本教程中,咱們將瞭解在Java中檢查null的必要性以及幫助咱們避免在代碼中進行空檢查的各類替代方法。程序員
根據 Javadoc for NullPointerException,當應用程序在須要對象的狀況下嘗試使用null時拋出它,例如:sql
讓咱們快速查看致使此異常的Java代碼的幾個示例:apache
publicvoid doSomething(){ String result = doSomethingElse(); if(result.equalsIgnoreCase("Success")) // success } } privateString doSomethingElse(){ returnnull; }
在這裏,咱們嘗試調用null引用的方法調用。這將致使NullPointerException。另外一個常見示例是,若是咱們嘗試訪問空數組:數組
publicstaticvoid main(String[] args){ findMax(null); } privatestaticvoid findMax(int[] arr){ int max = arr[0]; //check other elements in loop }
這會在第6行致使 NullPointerException。所以,訪問空 對象的任何字段,方法或索引會致使 NullPointerException,如上面的示例所示。避免 NullPointerException的 常見方法是檢查 null:安全
publicvoid doSomething(){ String result = doSomethingElse(); if(result !=null&& result.equalsIgnoreCase("Success")){ // success } else // failure } privateString doSomethingElse(){ returnnull; }
在現實世界中,程序員發現很難識別哪些對象能夠爲 null。積極安全的策略多是爲每一個對象檢查 null。可是,這會致使大量冗餘空值檢查,並使咱們的代碼可讀性下降。在接下來的幾節中,咱們將介紹Java中的一些備選方案,以免這種冗餘。架構
如上一節所述,訪問null對象的方法或變量會致使NullPointerException。 咱們還討論了在訪問對象以前對對象進行空 檢查能夠消除NullPointerException的可能性。可是,一般有API能夠處理空值。例如:併發
publicvoid print(Object param){ System.out.println("Printing "+ param); } publicObject process()throwsException{ Object result = doSomething(); if(result ==null){ thrownewException("Processing fail. Got a null response"); } else{ return result; } }
在 print()方法調用將只打印 null,但不會拋出異常。一樣, process()永遠不會在其響應中返回 null。它反而拋出異常。所以對於訪問上述API的客戶端代碼,不須要進行空檢查。可是此類API必須在約定中明確說明。API發佈此類約定的常見位置是JavaDoc。可是,這並未明確指出API約定,所以依賴於客戶端代碼開發人員來確保其合規性。在下一節中,咱們將看到一些IDE和其餘開發工具如何幫助開發人員解決這個問題。app
靜態代碼分析工具備助於提升代碼質量。一些這樣的工具也容許開發人員維護null約定(Null Contracts)。一個例子是 FindBugs。 FindBugs經過 @Nullable和 @NonNull註解幫助管理null約定。咱們能夠在任何方法,字段,局部變量或參數上使用這些註釋。這使得對客戶端代碼明確指出註釋類型是否爲 null。咱們來看一個例子:分佈式
publicvoid accept(@NonnullObject param){ System.out.println(param.toString()); }
在這裏, @NonNull清楚地代表參數不能爲 null。若是客戶端代碼在不檢查 null參數的狀況下調用此方法 ,則 FindBugs將在編譯時生成警告。函數
開發人員一般依靠IDE來編寫Java代碼。使用代碼自動補全和有用警告等功能,例如可能沒有聲明變量,在很大程度上對編碼有幫助。一些IDE還容許開發人員管理API約定(API Contracts),從而消除對靜態代碼分析工具的需求。IntelliJ IDEA提供 @NonNull和 @Nullable註解。要在IntelliJ中添加對這些註釋的支持,咱們必須添加如下Maven依賴項:
<dependency> <groupId>org.jetbrains</groupId> <artifactId>annotations</artifactId> <version>16.0.2</version> </dependency>
如今,若是沒有對 Null進行檢查,IntelliJ將生成警告,就像咱們在上一個示例中同樣。IntelliJ還提供了用於處理複雜API約束的Contract註釋。
到目前爲止,咱們只討論過從客戶端代碼中去除空檢查的必要性。可是,這不多適用於實際應用。如今,假設咱們正在使用一個不能接受空參數的API,或者能夠返回必須由客戶端處理的空響應。這代表咱們須要檢查參數或空值的響應。這裏,咱們可使用Java Assertions代替傳統的 null檢查條件語句:
publicvoid accept(Object param){ assert param !=null; doSomething(param); }
在第2行中,咱們檢查null參數。若是啓用了斷言,則會致使 AssertionError。儘管這是斷言非空參數等前置條件的好方法,但這種方法主要存在兩個問題:
所以,建議程序員不要使用斷言來檢查條件。在如下部分中,咱們將討論處理null檢查的其餘方法
編寫早期失敗的代碼一般是一種很好的作法。所以,若是一個API不容許接受有多個參數爲空,更好地方法是預先檢查API中的每個非空參數。
例如,讓咱們看看兩個方法:一個早期失敗,另外一個不失敗:
publicvoid goodAccept(String one,String two,String three){ if(one ==null|| two ==null|| three ==null){ thrownewIllegalArgumentException(); } process(one); process(two); process(three); } publicvoid badAccept(String one,String two,String three){ if(one ==null){ thrownewIllegalArgumentException(); } else{ process(one); } if(two ==null){ thrownewIllegalArgumentException(); } else{ process(two); } if(three ==null){ thrownewIllegalArgumentException(); } else{ process(three); } }
顯然,咱們應該更喜歡 goodAccept()而不是 badAccept()。做爲替代方案,咱們也可使用Guava的前置條件來驗證API參數。
因爲 null對於像int這樣的原語來講不是一個可接受的值,咱們應該儘量優先於它們的包裝對象,如 Integer。考慮一個對兩個整數求和的方法的兩個實現:
publicstaticint primitiveSum(int a,int b){ return a + b; } publicstaticInteger wrapperSum(Integer a,Integer b){ return a + b; }
有時,咱們須要將一個集合做爲方法的響應返回。對於這樣的方法,咱們應該老是嘗試返回一個空集合而不是 null
publicList<String> names(){ if(userExists()){ returnStream.of(readName()).collect(Collectors.toList()); } else{ returnCollections.emptyList(); } }
所以,咱們在調用此方法時避免了客戶端執行空檢查的須要。
Java 7引入了新的Objects API。此API有幾個靜態 實用程序方法,能夠消除大量冗餘代碼。讓咱們看看一個這樣的方法, requireNonNull():
publicvoid accept(Object param){ Objects.requireNonNull(param); // doSomething() }
如今,讓咱們測試 accept方法:
assertThrows(NullPointerException.class,()-> accept(null));
所以,若是將null 做爲參數傳遞,則 accept()會拋出 NullPointerException。此類還具備 isNull()和 nonNull()方法,可用做謂詞來檢查對象是否爲null。
Java8在該語言中引入了一個新的 OptionalAPI。與null相比,這爲處理可選值提供了更好的約定。讓咱們看看 Optional如何消除對空檢查的需求:
publicOptional<Object> process(Boolean processed){ String response = doSomething(processed); if(response ==null){ returnOptional.empty(); } returnOptional.of(response); } privateString doSomething(Boolean processed){ if(processed){ return"passed"; } else{ returnnull; } }
經過返回一個 Optional,如上所示,該 process()方法使得明確告訴調用者,響應多是Null,而且必須在編譯時處理。 這顯然消除了客戶端代碼中對空檢查的需求。可使用 OptionalAPI的聲明性樣式以不一樣方式處理空響應:
assertThrows(Exception.class,()-> process(false).orElseThrow(()->newException()));
此外,它還爲API開發人員提供了一個更好的約定,以向客戶端代表API能夠返回空響應。雖然咱們不須要對此API的調用者進行空檢查,但咱們使用它來返回空響應。爲避免這種狀況, Optional提供了一個 ofNullable方法,該方法返回具備指定值的 Optional,若是值爲 null,則返回 empty:
publicOptional<Object> process(Boolean processed){ String response = doSomething(processed); returnOptional.ofNullable(response); }
Lombok是一個很棒的庫,能夠減小項目中樣板代碼的數量。它附帶了一組註釋,取代了咱們常常在Java應用程序中編寫的代碼的常見部分,例如getter,setter和toString(),僅舉幾例。
另外一個註釋是 @NonNull。 所以,若是項目已經使用Lombok來消除樣板代碼,則 @NonNull能夠代替做爲空檢查。
在繼續查看一些示例以前,添加一個Maven依賴項引入Lombok:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version> </dependency>
如今,咱們能夠在須要進行空檢查的地方 使用 @NonNull:
publicvoid accept(@NonNullObject param){ System.out.println(param); }
所以,咱們只是註解了須要進行null檢查的對象,而且Lombok生成了已編譯的類:
publicvoid accept(@NonNullObject param){ if(param ==null){ thrownewNullPointerException("param"); } else{ System.out.println(param); } }
若是 param爲null,則此方法拋出 NullPointerException。該方法必須在其約定中明確說明,而且客戶端代碼必須處理異常。
通常來講,字符串驗證包括除空值檢查空值。所以,常見的驗證聲明是:
publicvoid accept(String param){ if(null!= param &&!param.isEmpty()) System.out.println(param); }
若是咱們必須處理不少 String類型,這很快就會變得多餘。這就是 StringUtils派上用場的地方。在咱們看到這個動做以前,讓咱們爲commons-lang3添加一個Maven依賴項:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency>
如今讓咱們用 StringUtils重構上面的代碼 :
publicvoid accept(String param){ if(StringUtils.isNotEmpty(param)) System.out.println(param); }
所以,咱們使用靜態實用程序方法 isNotEmpty()替換了 null或空檢查。此API提供了其它強大而實用方法來處理常見的String函數。
在本文中,咱們研究了發生 NullPointerException的各類緣由以及難以識別的緣由。而後,咱們使用了各類方法來避免代碼中的冗餘,以及對使用參數,返回類型和其餘變量進行空檢查。全部示例均可以在GitHub上找到。
分享免費學習資料
針對於Java程序員,我這邊準備免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個小贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!
資料領取方式:加入Java技術交流羣963944895
,點擊加入羣聊,私信管理員便可免費領取