妳的問題的解決方案
問題位置:
在堆棧異常信息的第壹行,您可以找到空指針的位置。如果這不是妳寫的類,妳可以向下滾動找到妳寫的類,就是這裏出現的空指針。
解決問題:
當調用空對象中的方法或屬性時,它將報告壹個空指針並檢查對象為什麽是空的。
Java空指針異常的幾種解決方案
Java中的任何對象都可能是空的。當我們調用壹個空對象的方法時,我們會拋出壹個NullPointerException,這是壹個非常常見的錯誤類型。我們可以使用幾種方法來避免這種異常,並使我們的代碼更加健壯。本文將列出這些解決方案,包括傳統的空檢測、編程規範以及現代Java語言引入的各種工具作為輔助。
運行時檢測
最明顯的方法是使用if (obj == null)檢測所有需要使用的對象,包括函數參數、返回值和類實例的成員變量。當檢測到空值時,可以選擇拋出更有針對性的異常類型,如IllegalArgumentException,並添加消息內容。我們可以使用壹些庫函數來簡化代碼,比如壹開始Java 7提供的Objects#requireNonNull方法:
public void testObjects(Object arg){
object checked = objects . require nonnull(arg," arg不得為null ");
checked . tostring();}
Guava的前提條件類還提供了壹系列檢測參數合法性的工具函數,包括空值檢測:
public void test guava(Object arg){
object checked = preceditions . checknotNull(arg," %s不得為null "," arg ");
checked . tostring();
}
我們還可以使用Lombok生成空檢測代碼,並拋出壹個空指針異常,並給出提示信息:
public void test lombok(@ NonNull Object arg){
arg . tostring();
生成的代碼如下:
public void testlombokkgenerated(Object arg){
if (arg == null) {
拋出new NullPointerException("arg被標記為@NonNull,但為null ");
}
arg . tostring();
}
這個註釋也可以用在類實例的成員變量上,所有的賦值操作都會自動檢測空值。
編程規範
通過遵守壹些編程規範,也可以在壹定程度上減少空指針異常的發生。
使用已經判斷空值的方法,如String#equals、String#valueOf和三方庫中使用的函數來判斷字符串和集合是否為空:
if (str!= null & amp& ampstr . equals(" text "){ }
if("文本"。equals(str)) {}
if (obj!= null){ obj . tostring();}
string . value of(obj);//"空"
//來自彈簧芯
string utils . isempty(str);
collection utils . isempty(col);
//來自番石榴
strings . isnullorempty(str);
//來自commons-collections4
collection utils . isempty(col);
如果壹個函數的參數可以接收null值,可以考慮將其重寫為兩個函數,並使用不同的函數簽名,這樣可以強制每個參數不為空:
public void method a(Object arg 1){
methodB(arg1,新對象[0]);
}
public void method b(Object arg 1,Object[] arg2) {
for (Object obj : arg2) {} //無空檢查
}
如果函數的返回值是集合類型,當結果為空時,不返回null值,而是返回壹個空集;如果返回值類型是對象,則可以選擇引發異常。這是Spring JdbcTemplate的處理方式:
//當查詢結果為空時,返回新的ArrayList
JDBC template . query forlist(" SELECT * FROM person ");
//如果找不到記錄,將引發EmptyResultDataAccessException。
JDBC template . query for object(" SELECT age FROM person WHERE id = 1 ",integer . class);
//支持泛型集合
公共& ltT & gt列表& ltT & gttestReturnCollection() {
返回collections . empty list();
}
靜態代碼分析
Java語言中有很多靜態代碼分析工具,如Eclipse IDE、SpotBugs、Checker Framework等。,它可以幫助程序員檢測編譯時錯誤。結合@Nullable和@Nonnull等註釋,我們可以在程序運行前找到可能拋出空指針異常的代碼。
然而,零檢測的註釋還沒有被標準化。雖然社區在2006年9月提出了JSR 305規範,但它已經被擱置了很長時間。許多第三方庫提供類似的註釋,並由不同的工具支持,其中包括:
Javax.annotation.Nonnull:由JSR 305提出,參考實現為com . Google . code . FindBugs . JSR 305;
Org。Eclipse . JDT . annotation . nonnull:Eclipse IDE原生支持的空檢測註釋;
edu . UMD . cs . FindBugs . Annotations . nonnull:spot bugs使用的註釋,基於findbugs.jsr305;
org . Spring Framework . lang . nonnull:Spring Framework 5.0可用;
org . Checker framework . Checker . nullness . qual . nonnull:Checker framework使用;
Android . support . annotation . nonnull:集成在Android開發工具中;
我建議使用跨IDE的解決方案,比如SpotBugs或者Checker Framework,可以和Maven很好的結合。
SpotBugs和@NonNull,@CheckForNull
SpotBugs是FindBugs的繼承者。通過向方法的參數和返回值添加@NonNull和@CheckForNull註釋,SpotBugs可以幫助我們在編譯時檢測空值。需要註意的是,SpotBugs不支持@Nullable批註,必須替換為@CheckForNull。如官方文檔所述,@Nullable只在需要覆蓋@ParametersAreNonnullByDefault時使用。
官方文檔解釋了如何將SpotBugs應用於Maven和Eclipse。我們還需要將spotbugs-annotations添加到項目依賴項中,以使用相應的註釋。
& lt依賴性& gt
& ltgroupId & gtcom . github . spot bugs & lt;/groupId & gt;
& ltartifactId & gtspot bugs-註釋& lt/artifact id & gt;
& lt版本& gt3.1.7 & lt;/version & gt;
& lt/dependency & gt;
以下是不同使用場景的描述:
@NonNull
私有對象returnNonNull() {
//錯誤:returnNonNull()可能返回Null,但它已被聲明為@Nonnull。
返回null
}
@CheckForNull
私有對象returnNullable() {
返回null
}
public void testReturnNullable(){
object obj = return nullable();
//錯誤:方法的返回值可能為空。
system . out . println(obj . tostring());
}
私有void argumentNonNull(@ NonNull Object arg){
system . out . println(arg . tostring());
}
public void testArgumentNonNull(){
//錯誤:不能將null傳遞給非空參數。
argumentNonNull(null);
}
public void testNullableArgument(@ CheckForNull Object arg){
//錯誤:參數可能為空。
system . out . println(arg . tostring());
}
對於Eclipse用戶,也可以使用IDE內置的null檢測工具,只需替換默認的annotation org即可。帶有SpotBugs註釋的eclipse . JDT . annotation . nullable:
Checker框架和@NonNull,@Nullable
Checker Framework可以作為javac編譯器的插件運行,可以檢測代碼中的數據類型,防止出現各種問題。我們可以參考官方文檔,把Checker框架和maven-compiler-plugin結合起來,然後在每次執行mvn compile的時候進行檢查。Checker Framework的空檢測器幾乎支持所有註釋,包括JSR 305、Eclipse,甚至lombok.NonNull
導入org . checker framework . checker . nullness . qual . nullable;
@Nullable
私有對象returnNullable() {
返回null
}
public void testReturnNullable(){
object obj = return nullable();
//錯誤:obj可能為空。
system . out . println(obj . tostring());
}
默認情況下,Checker Framework會將@NonNull應用於所有函數參數和返回值。因此,即使不添加此註釋,下列程序也無法編譯:
私有對象returnNonNull() {
//錯誤:該方法被聲明為@NonNull,但返回Null。
返回null
}
私有void argumentNonNull(Object arg){
system . out . println(arg . tostring());
}
public void testArgumentNonNull(){
//錯誤:該參數被聲明為@NonNull,但傳入了Null。
argumentNonNull(null);
}
Checker Framework對於使用Spring Framework 5.0或以上版本的用戶來說非常有用,因為Spring提供了內置的空檢測註釋,並且可以被Checker Framework支持。壹方面,我們不需要引入額外的Jar包,更重要的是,Spring框架代碼本身就使用了這些註釋,這樣我們在調用它的API時就可以有效地處理空值。比如StringUtils類中可以傳入null值的函數和返回null值的函數都添加了@Nullable註釋,而沒有添加的方法則繼承了整個框架的@NonNull註釋。因此,Checker框架可以檢測到以下代碼中的空指針異常:
//這是spring-core中定義的類和方法。
公共抽象類StringUtils {
str參數繼承了global @NonNull批註。
公共靜態字符串大寫(String str) {}
@Nullable
公共靜態字符串getFilename(@可空字符串路徑){}
}
//錯誤:該參數被聲明為@NonNull,但傳入了Null。
string utils . capital(null);
string filename = string utils . get filename("/path/to/file ");
//錯誤:文件名可能為空。
system . out . println(filename . length());
可選類型
Java 8引入了可選
可選& lt字符串& gtopt
//創建
opt = optional . empty();
opt = optional . of(" text ");
opt = optional . of nullable(null);
//判斷並閱讀
if (opt.isPresent()) {
opt . get();
}
//默認值
opt . or else(" default ");
opt . orelseget(()-& gt;“默認”);
opt . orelsthrow(()-& gt;new NullPointerException());
//相關操作
opt . if present(value-& gt;{
System.out.println(值);
});
opt.filter(值-& gt;value . length()& gt;5);
opt . map(value-& gt;value . trim());
opt . flat map(value-& gt;{
string trimmed = value . trim();
返回trimmed.isEmpty()?optional . empty():optional . of(trimmed);
});
方法的鏈調用很容易導致空指針異常,但如果返回值都包裝在Optional中,則可以使用flatMap方法實現安全的鏈調用:
String zipCode = getUser()
。平面圖(User::getAddress)
。平面圖(地址::getZipCode)
。orElse(" ");
Java 8流API也使用Optional作為返回類型:
stringList.stream()。findFirst()。orElse("默認");
stringList.stream()
。max(Comparator.naturalOrder())
。if present(system . out::println);
此外,Java 8還為基本類型提供了單獨的可選類,如OptionalInt和OptionalDouble,非常適合對性能要求較高的場景。
其他JVM語言中的空指針異常
Scala語言中的Option類對於Java 8可以是可選的。它有兩個子類型,壹些代表值,沒有代表空值。
val opt:Option[String]= Some(" text ")
opt.getOrElse("default ")
除了使用選項#isEmpty判斷,還可以使用Scala的模式匹配:
選擇匹配{
case Some(text)= & gt;打印(文本)
case None = & gtprintln("默認")
Scala的集合處理函數庫非常強大,Option可以直接作為集合操作,比如filer、map、for-analysis:
opt.map(_。修剪)。過濾器(_)。長度& gt0).地圖(_。toUpperCase)。getOrElse("DEFAULT ")
val upper = for {
text & lt- opt
修剪& lt- Some(text.trim())
upper & lt-壹些(修剪過的),如果修剪過的話。0
}收益率上限
upper.getOrElse("DEFAULT ")
Kotlin使用了另壹種方式,用戶在定義變量時需要明確區分可空類型和不可空類型。當使用可空類型時,必須進行空值檢測。
var a: String = "text "
A = null //錯誤:不能將null賦給非空字符串類型。
val b:字符串?= "文本"
//錯誤:必須使用安全運算符(?。)或者強制忽略(!!。)。
打印長度
val l: Int?= b?。長度//安全操作
b!!。length //強制忽略,這可能會引發空異常。
Kotlin的特點之壹是與Java的互操作性,但是Kotlin編譯器無法知道Java類型是否為空,所以需要在Java代碼中使用註釋,而Kotlin支持的註釋範圍很廣。從Spring Framework 5.0開始,Kotlin得到了原生支持,其空值檢測也是通過註釋進行的,這樣Kotlin就可以安全地調用Spring Framework的所有API。
結論
在上述方案中,我推薦使用註釋來防止空指針異常,因為這種方法非常有效,而且對代碼的侵害較小。所有的公共* *** API都應該用@Nullable和@NonNull進行註釋,這可以強制調用者防止空指針異常,使我們的程序更加健壯。