作者:肖漢松
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. ()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 中 String 對 null 物件的容錯處理,上面的例子沒有覆寫所有的處理情況,算是拋磚引玉,
如何讓程式中的 null 物件在我們的控制之中,是我們編程的時候需要時刻注意的事情,
推薦去我的博客閱讀更多:
1.Java JVM、集合、多執行緒、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架構、阿里巴巴等大廠最新面試題
覺得不錯,別忘了點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/187930.html
標籤:Java
上一篇:簡單了解 HTTP 協議.
下一篇:搭建Java環境
