String拼接出現Null?你看到的分析可是錯的
本文轉載自微信公眾號「程序新視界」,作者二師兄 。轉載本文請聯系程序新視界公眾號。
前言
String類型真是個神奇的存在,動不動就會出現一些迷惑人的錯誤。今天看到一篇文中提到當String的值為null時,進行字符串相加拼接,會出現把null當做字符串拼接的現象。
比如下面這段代碼:
- String s = null;
- s = s + "hello";
- System.out.println(s + " world");
你預期的結果可能是“hello world”,但實際的結果是“nullhello world”,神奇吧。
其實這倒沒什么,實踐一下就可以看到結果。但當你好奇心作祟,在網上搜為什么時,你看到的答案可能是錯的。
我在搜索時,看到訪問量上萬的文章給出的解釋竟然錯誤的。為了排除一些誤導,特意為大家分析一下原因。
錯誤的原因分析
如果對上述問題進行搜索,你可能看到的答案是:
- s + " world" 等價于 s = String.valueOf(s)+"word";
然后附帶valueOf方法:
- public static String valueOf(Object obj) {
- return (obj == null) ? "null" : obj.toString();
- }
你信了嗎?如果信了可能真的就錯了。下面我們就來分析分析為什么錯了。
Java編譯器的優化
我們知道,當我們寫下面的代碼時Java編譯器會為我們做一些優化:
- String a = "Hello ";
- String b = "World";
- System.out.println(a + b);
如何優化的?上面這段代碼經過編譯器優化之后,等價于:
- StringBuilder sb = new StringBuilder();
- sb.append("Hello ");
- sb.append("World");
- String result = sb.toString();
- System.out.println(result);
也就是說,加號操作會被優化基于StringBuilder的操作,而并不是上面提到的String.valueOf操作。
那么,上面為null的情況也就等價于下面的操作了:
- StringBuilder sb = new StringBuilder(null);
- sb.append("hello");
- sb.append(" world");
- String result = sb.toString();
- System.out.println(result);
此時,我們再看一下StringBuilder(null)這個構造方法的底層實現,最終調到它的父類AbstractStringBuilder中的append方法:
- 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;
- }
對應的appendNull方法實現為:
- 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;
- }
在appendNull方法中就是將null當做字符串“null”來處理了。這也就是為什么會在拼接中出現null的原因。
字節碼追蹤
針對上述示例,如果你想看編譯器是如何處理的,可以通過javap -c 命令來查看對應字節碼:
通過字節碼可以看出,基本上與上面的分析的一致。所以說,盡信書不如無書。
拓展問題
解決了上述問題,再來看看,如果我們單純的就打印null是怎么輸出的?
- String s = null;
- System.out.println(s);
執行上述程序,控制臺打印null,這個null是哪兒來的呢?直接看println的底層實現:
- public void print(String s) {
- if (s == null) {
- s = "null";
- }
- write(s);
- }
最終調用到了print方法,如果為null,則打印null字符串。
支持,還沒有出現最初的valueOf方法,那么valueOf方法在什么場景下會用到呢?在對象為Object類型時:
- Object s = null;
- String s1 = String.valueOf(s);
- System.out.println(s1);
也就是說在明確調用valueOf方法時,此時s1的值直接是null字符串。
再拓展一下,針對一些基礎類型的包裝類,比如Integer、Double等:
- Integer i = null;
- System.out.println(i);
上述代碼的處理又不太一樣,println方法實現如下:
- public void println(Object x) {
- String s = String.valueOf(x);
- synchronized (this) {
- print(s);
- newLine();
- }
- }
也就是說先對對應的Object對象調用valueOf,回到上面的示例,如果Object為null,該方法返回null字符串,后續打印機直接為null。
小結
字符串拼接是很常見的問題,一不小心會出現將null給拼接上的情況。而這狀況的出現又牽扯到Java編譯器的優化,是不是很有意思?而且正如最開始所述,當我們在網絡上搜索資料時也要辨證的去看待答案的準確性。