Java 中關於 null 物件的容錯處理
來源:blog.xiaohansong.com/2016/03/13/null-in-java-string/
在 Thinking in Java 看到這樣一段話:
Primitives that are fields in a class are automatically initialized to zero, as noted in the Everything Is an Object chapter. But the object references are initialized to null, and if you try to call methods for any of them, you’ll get an exception-a runtime error. Conveniently, you can still print a null reference without throwing an exception.
大意是:原生型別會被自動初始化為 0,但是物件引用會被初始化為 null,如果你嘗試呼叫該物件的方法,就會丟擲空指標異常。通常,你可以列印一個 null 物件而不會丟擲異常。
第一句相信大家都會容易理解,這是型別初始化的基礎知識,但是第二句就讓我很疑惑:為什麼列印一個 null 物件不會丟擲異常?帶著這個疑問,我開始瞭解惑之旅。下面我將詳細闡述我解決這個問題的思路,並且深入 JDK 原始碼找到問題的答案。
解決問題的過程
可以發現,其實這個問題有幾種情況,所以我們分類討論各種情況,看最後能不能得到答案。
首先,我們把這個問題分解為三個小問題,逐一解決。
第一個問題
直接列印 null 的 String 物件,會得到什麼結果?
String s = null;
System.out.print(s);
執行的結果是
null
果然如書上說的沒有丟擲異常,而是列印了null。顯然問題的線索在於print函式的原始碼中。我們找到print的原始碼:
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
看到原始碼才發現原來就只是加了一句判斷而已,簡單粗暴,可能你對 JDK 的簡單實現有點失望了。放心,第一個問題只是開胃菜而已,大餐還在後面。
第二個問題
列印一個 null 的非 String 物件,例如說 Integer:
Integer i = null;
System.out.print(i);
執行的結果不出意料:
null
我們再去看看print的原始碼:
public void print(Object obj) {
write(String.valueOf(obj));
}
有點不一樣的了,看來祕密藏在valueOf裡面。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
看到這裡,我們終於發現了列印 null 物件不會丟擲異常的祕密。print方法對 String 物件和非 String 物件分開進行處理。
String 物件:直接判斷是否為 null,如果為 null 給 null 物件賦值為”null”。
非 String 物件:通過呼叫String.valueOf方法,如果是 null 物件,就返回”null”,否則呼叫物件的toString方法。
通過上面的處理,可以保證列印 null 物件不會出錯。
到這裡,本文就應該結束了。
什麼?說好的大餐呢?上面還不夠塞牙縫呢。
開玩笑啦。下面我們來探討第三個問題。
第三個問題(隱藏的大餐)
null 物件與字串拼接會得到什麼結果?
String s = null;
s = s + "!";
System.out.print(s);'
結果可能你也猜到了:
null!
為什麼呢?跟蹤程式碼執行可以發現,這回跟print沒有什麼關係。但是上面的程式碼就呼叫了print函式,不是它會是誰呢?+的嫌疑最大,但是+又不是函式,我們怎麼看到它的原始碼?這種情況,唯一的解釋就是編譯器動了手腳,天網恢恢,疏而不漏,找不到原始碼,我們可以去看看編譯器生成的位元組碼。
L0
LINENUMBER 27 L0
ACONST_NULL
ASTORE 1
L1
LINENUMBER 28 L1
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "!"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 1
L2
LINENUMBER 29 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V
看了上面的位元組碼是不是一頭霧水?這裡我們就要扯開話題,來侃侃+字串拼接的原理了。
編譯器對字串相加會進行優化,首先例項化一個StringBuilder,然後把相加的字串按順序append,最後呼叫toString返回一個String物件。不信你們看看上面的位元組碼是不是出現了StringBuilder。詳細的解釋參考這篇文章 Java細節:字串的拼接。
String s = "a" + "b";
//等價於
StringBuilder sb = new StringBuilder();
sb.append("a");
sb.append("b");
String s = sb.toString();
再回到我們的問題,現在我們知道祕密在StringBuilder.append函式的原始碼中。
//針對 String 物件
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//針對非 String 物件
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
現在我們恍然大悟,append函式如果判斷物件為 null,就會呼叫appendNull,填充”null”。
(完)
Java團長
專注於Java乾貨分享
掃描上方二維碼獲取更多Java乾貨
相關文章
- Java--- 關於null的處理若干方法JavaNull
- 關於Python中的日期處理Python
- 【C#】-對於Null值的處理方法C#Null
- Flink快照容錯處理
- 關於Java中的類和物件筆記Java物件筆記
- 關於attention中對padding的處理:maskpadding
- 物件轉json字串的過程中對value為null的值的一些處理物件JSON字串Null
- 關於laravel的錯誤頁面處理大家都是如何優雅的處理的呢?Laravel
- JDBC中Date日期物件的處理JDBC物件
- grpc中的錯誤處理RPC
- ORACLE GoldenGate 使用技巧-容錯處理等OracleGo
- 基於 React Redux 的錯誤處理ReactRedux
- vscode關於json檔案新增註釋報錯處理VSCodeJSON
- 關於事件物件中的stopImmediatePropagation事件物件
- kafka 副本機制和容錯處理 -2Kafka
- Restful API 中的錯誤處理RESTAPI
- 【譯】RxJava 中的錯誤處理RxJava
- 如何處理 Spring Boot 中與快取相關的錯誤?Spring Boot快取
- Spring Boot 中關於自定義異常處理的套路!Spring Boot
- java當中的批處理Java
- Java 中的並行處理Java並行
- django2中關於時間處理策略Django
- angular(2+)報錯處理之 -- 關於function 、lambda、not supported等AngularFunction
- 關於go的跨域處理 ginGo跨域
- 關於Android的幾種事件處理Android事件
- 關於linux病毒`kinsing` `kdevtmpfsi`的處理Linuxdev
- 關於GCD多工處理GC
- 關於php rsa加密處理PHP加密
- 談談RxSwift中的錯誤處理Swift
- 應用中的錯誤處理概述
- Bash 指令碼中的錯誤處理指令碼
- 關於高併發和分散式中的冪等處理分散式
- FastJson 序列化處理 null 值ASTJSONNull
- Java中對時間的處理Java
- java中的垃圾處理機制Java
- 關於特殊符號&與& 的處理符號
- Java Sting類關於split處理空串以及擷取最大數Java
- 關於對於Java中Entity以及VO,以及DTO中Request物件序列化的學習Java物件