java 删除二维数组中的null_避免在Java中检查Null语句
1.概述
通常,在Java代碼中處理null變量、引用和集合很棘手。它們不僅難以識(shí)別,而且處理起來(lái)也很復(fù)雜。事實(shí)上,在編譯時(shí)無(wú)法識(shí)別處理null的任何錯(cuò)誤,會(huì)導(dǎo)致運(yùn)行時(shí)NullPointerException。在本教程中,我們將了解在Java中檢查null的必要性以及幫助我們避免在代碼中進(jìn)行空檢查的各種替代方法。
2.什么是NullPointerException?
根據(jù) Javadoc for NullPointerException,當(dāng)應(yīng)用程序在需要對(duì)象的情況下嘗試使用null時(shí)拋出它,例如:
- 調(diào)用null對(duì)象的實(shí)例方法
- 訪問(wèn)或修改空對(duì)象的字段
- 取null的長(zhǎng)度,就好像它是一個(gè)數(shù)組一樣
- 訪問(wèn)或修改null的插槽,就像它是一個(gè)數(shù)組一樣
- 拋出null就好像它是一個(gè)Throwable值
讓我們快速查看導(dǎo)致此異常的Java代碼的幾個(gè)示例:
publicvoid doSomething(){String result = doSomethingElse();if(result.equalsIgnoreCase("Success"))// success}}privateString doSomethingElse(){returnnull;}在這里,我們嘗試調(diào)用null引用的方法調(diào)用。這將導(dǎo)致NullPointerException。另一個(gè)常見(jiàn)示例是,如果我們嘗試訪問(wèn)空數(shù)組:
publicstaticvoid main(String[] args){findMax(null);}privatestaticvoid findMax(int[] arr){int max = arr[0];//check other elements in loop}這會(huì)在第6行導(dǎo)致 NullPointerException。因此,訪問(wèn)空 對(duì)象的任何字段,方法或索引會(huì)導(dǎo)致 NullPointerException,如上面的示例所示。避免 NullPointerException的 常見(jiàn)方法是檢查 null:
publicvoid doSomething(){String result = doSomethingElse();if(result !=null&& result.equalsIgnoreCase("Success")){// success}else// failure}privateString doSomethingElse(){returnnull;}在現(xiàn)實(shí)世界中,程序員發(fā)現(xiàn)很難識(shí)別哪些對(duì)象可以為 null。積極安全的策略可能是為每個(gè)對(duì)象檢查 null。但是,這會(huì)導(dǎo)致大量冗余空值檢查,并使我們的代碼可讀性降低。在接下來(lái)的幾節(jié)中,我們將介紹Java中的一些備選方案,以避免這種冗余。
3.通過(guò)API約定處理null
如上一節(jié)所述,訪問(wèn)null對(duì)象的方法或變量會(huì)導(dǎo)致NullPointerException。 我們還討論了在訪問(wèn)對(duì)象之前對(duì)對(duì)象進(jìn)行空 檢查可以消除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()方法調(diào)用將只打印 null,但不會(huì)拋出異常。同樣, process()永遠(yuǎn)不會(huì)在其響應(yīng)中返回 null。它反而拋出異常。因此對(duì)于訪問(wèn)上述API的客戶端代碼,不需要進(jìn)行空檢查。但是此類(lèi)API必須在約定中明確說(shuō)明。API發(fā)布此類(lèi)約定的常見(jiàn)位置是JavaDoc。但是,這并未明確指出API約定,因此依賴于客戶端代碼開(kāi)發(fā)人員來(lái)確保其合規(guī)性。在下一節(jié)中,我們將看到一些IDE和其他開(kāi)發(fā)工具如何幫助開(kāi)發(fā)人員解決這個(gè)問(wèn)題。
4.自動(dòng)化API約定
4.1.使用靜態(tài)代碼分析
靜態(tài)代碼分析工具有助于提高代碼質(zhì)量。一些這樣的工具也允許開(kāi)發(fā)人員維護(hù)null約定(Null Contracts)。一個(gè)例子是 FindBugs。 FindBugs通過(guò) @Nullable和 @NonNull注解幫助管理null約定。我們可以在任何方法,字段,局部變量或參數(shù)上使用這些注釋。這使得對(duì)客戶端代碼明確指出注釋類(lèi)型是否為 null。我們來(lái)看一個(gè)例子:
publicvoid accept(@NonnullObject param){System.out.println(param.toString());}在這里, @NonNull清楚地表明參數(shù)不能為 null。如果客戶端代碼在不檢查 null參數(shù)的情況下調(diào)用此方法 ,則 FindBugs將在編譯時(shí)生成警告。
4.2.使用靜態(tài)代碼分析
開(kāi)發(fā)人員通常依靠IDE來(lái)編寫(xiě)Java代碼。使用代碼自動(dòng)補(bǔ)全和有用警告等功能,例如可能沒(méi)有聲明變量,在很大程度上對(duì)編碼有幫助。一些IDE還允許開(kāi)發(fā)人員管理API約定(API Contracts),從而消除對(duì)靜態(tài)代碼分析工具的需求。IntelliJ IDEA提供 @NonNull和 @Nullable注解。要在IntelliJ中添加對(duì)這些注釋的支持,我們必須添加以下Maven依賴項(xiàng):
<dependency><groupId>org.jetbrains</groupId><artifactId>annotations</artifactId><version>16.0.2</version></dependency>現(xiàn)在,如果沒(méi)有對(duì) Null進(jìn)行檢查,IntelliJ將生成警告,就像我們?cè)谏弦粋€(gè)示例中一樣。IntelliJ還提供了用于處理復(fù)雜API約束的Contract注釋。
5.斷言
到目前為止,我們只討論過(guò)從客戶端代碼中去除空檢查的必要性。但是,這很少適用于實(shí)際應(yīng)用。現(xiàn)在,假設(shè)我們正在使用一個(gè)不能接受空參數(shù)的API,或者可以返回必須由客戶端處理的空響應(yīng)。這表明我們需要檢查參數(shù)或空值的響應(yīng)。這里,我們可以使用Java Assertions代替?zhèn)鹘y(tǒng)的 null檢查條件語(yǔ)句:
publicvoid accept(Object param){assert param !=null;doSomething(param);}在第2行中,我們檢查null參數(shù)。如果啟用了斷言,則會(huì)導(dǎo)致 AssertionError。盡管這是斷言非空參數(shù)等前置條件的好方法,但這種方法主要存在兩個(gè)問(wèn)題:
因此,建議程序員不要使用斷言來(lái)檢查條件。在以下部分中,我們將討論處理null檢查的其他方法
6.通過(guò)編碼實(shí)踐避免NULL檢查
6.1.前提條件
編寫(xiě)早期失敗的代碼通常是一種很好的做法。因此,如果一個(gè)API不允許接受有多個(gè)參數(shù)為空,更好地方法是預(yù)先檢查API中的每一個(gè)非空參數(shù)。
例如,讓我們看看兩個(gè)方法:一個(gè)早期失敗,另一個(gè)不失敗:
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);}}顯然,我們應(yīng)該更喜歡 goodAccept()而不是 badAccept()。作為替代方案,我們也可以使用Guava的前置條件來(lái)驗(yàn)證API參數(shù)。
6.2.使用原語(yǔ)而不是包裝類(lèi)
由于 null對(duì)于像int這樣的原語(yǔ)來(lái)說(shuō)不是一個(gè)可接受的值,我們應(yīng)該盡可能優(yōu)先于它們的包裝對(duì)象,如 Integer。考慮一個(gè)對(duì)兩個(gè)整數(shù)求和的方法的兩個(gè)實(shí)現(xiàn):
publicstaticint primitiveSum(int a,int b){return a + b;}publicstaticInteger wrapperSum(Integer a,Integer b){return a + b;}6.3.空集合
有時(shí),我們需要將一個(gè)集合作為方法的響應(yīng)返回。對(duì)于這樣的方法,我們應(yīng)該總是嘗試返回一個(gè)空集合而不是 null
publicList<String> names(){if(userExists()){returnStream.of(readName()).collect(Collectors.toList());}else{returnCollections.emptyList();}}因此,我們?cè)谡{(diào)用此方法時(shí)避免了客戶端執(zhí)行空檢查的需要。
7.使用 Objects
Java 7引入了新的Objects API。此API有幾個(gè)靜態(tài) 實(shí)用程序方法,可以消除大量冗余代碼。讓我們看看一個(gè)這樣的方法, requireNonNull():
publicvoid accept(Object param){Objects.requireNonNull(param);// doSomething()}現(xiàn)在,讓我們測(cè)試 accept方法:
assertThrows(NullPointerException.class,()-> accept(null));因此,如果將null 作為參數(shù)傳遞,則 accept()會(huì)拋出 NullPointerException。此類(lèi)還具有 isNull()和 nonNull()方法,可用作謂詞來(lái)檢查對(duì)象是否為null。
8.使用Optional
Java8在該語(yǔ)言中引入了一個(gè)新的 OptionalAPI。與null相比,這為處理可選值提供了更好的約定。讓我們看看 Optional如何消除對(duì)空檢查的需求:
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;}}通過(guò)返回一個(gè) Optional,如上所示,該 process()方法使得明確告訴調(diào)用者,響應(yīng)可能是Null,并且必須在編譯時(shí)處理。 這顯然消除了客戶端代碼中對(duì)空檢查的需求。可以使用 OptionalAPI的聲明性樣式以不同方式處理空響應(yīng):
assertThrows(Exception.class,()-> process(false).orElseThrow(()->newException()));此外,它還為API開(kāi)發(fā)人員提供了一個(gè)更好的約定,以向客戶端表明API可以返回空響應(yīng)。雖然我們不需要對(duì)此API的調(diào)用者進(jìn)行空檢查,但我們使用它來(lái)返回空響應(yīng)。為避免這種情況, Optional提供了一個(gè) ofNullable方法,該方法返回具有指定值的 Optional,如果值為 null,則返回 empty:
publicOptional<Object> process(boolean processed){String response = doSomething(processed);returnOptional.ofNullable(response);}9. 庫(kù)
9.1. 使用Lombok
Lombok是一個(gè)很棒的庫(kù),可以減少項(xiàng)目中樣板代碼的數(shù)量。它附帶了一組注釋,取代了我們經(jīng)常在Java應(yīng)用程序中編寫(xiě)的代碼的常見(jiàn)部分,例如getter,setter和toString(),僅舉幾例。
另一個(gè)注釋是 @NonNull。 因此,如果項(xiàng)目已經(jīng)使用Lombok來(lái)消除樣板代碼,則 @NonNull可以代替作為空檢查。
在繼續(xù)查看一些示例之前,添加一個(gè)Maven依賴項(xiàng)引入Lombok:
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency>現(xiàn)在,我們可以在需要進(jìn)行空檢查的地方 使用 @NonNull:
publicvoid accept(@NonNullObject param){System.out.println(param);}因此,我們只是注解了需要進(jìn)行null檢查的對(duì)象,并且Lombok生成了已編譯的類(lèi):
publicvoid accept(@NonNullObject param){if(param ==null){thrownewNullPointerException("param");}else{System.out.println(param);}}如果 param為null,則此方法拋出 NullPointerException。該方法必須在其約定中明確說(shuō)明,并且客戶端代碼必須處理異常。
9.2.使用StringUtils
一般來(lái)說(shuō),字符串驗(yàn)證包括除空值檢查空值。因此,常見(jiàn)的驗(yàn)證聲明是:
publicvoid accept(String param){if(null!= param &&!param.isEmpty())System.out.println(param);}如果我們必須處理很多 String類(lèi)型,這很快就會(huì)變得多余。這就是 StringUtils派上用場(chǎng)的地方。在我們看到這個(gè)動(dòng)作之前,讓我們?yōu)閏ommons-lang3添加一個(gè)Maven依賴項(xiàng):
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8.1</version></dependency>現(xiàn)在讓我們用 StringUtils重構(gòu)上面的代碼 :
publicvoid accept(String param){if(StringUtils.isNotEmpty(param))System.out.println(param);}因此,我們使用靜態(tài)實(shí)用程序方法 isNotEmpty()替換了 null或空檢查。此API提供了其它強(qiáng)大而實(shí)用方法來(lái)處理常見(jiàn)的String函數(shù)。
10.結(jié)論
在本文中,我們研究了發(fā)生 NullPointerException的各種原因以及難以識(shí)別的原因。然后,我們使用了各種方法來(lái)避免代碼中的冗余,以及對(duì)使用參數(shù),返回類(lèi)型和其他變量進(jìn)行空檢查。所有示例都可以在GitHub上找到。
總結(jié)
以上是生活随笔為你收集整理的java 删除二维数组中的null_避免在Java中检查Null语句的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android 快传 源码_最新安卓仿茄
- 下一篇: java已被弱化签名,高效Java第四十