沒事的時候,我並不喜歡逛 P 站,而喜歡逛 programcreek 這些技術型網站,於是那天晚上,在夜深人靜的時候,我就發現了一個專注基礎但不容忽視的主題。比如說:Java 中的 null 到底是什么鬼?像這類靈魂拷問的主題,非常值得深入地研究一下。

null 在 Java 中是一個特殊的存在,因為它和大名鼎鼎的 NullPointerException
(NPE)如影隨形。NPE 的發明人 Tony Hoare 曾在 2009 年承認:“Null References 是一個荒唐的設計,就好像我賭輸掉了十億美元”。
那為什么 Java 會一直保留着 null,而沒有把它消滅掉呢?我想是因為 null 的存在的確為 Java 帶來了更多好處(我在下文中指出了一些,看大家能不能發現哦)。

就讓我們從下面這個語句開始。
String s = null;
首先,我們來回顧一下:什么是變量,什么是值。舉個恰當的例子,變量就好像一個窯洞,里面可以住人,也可以不住人,人就相當於值。在 Java 中,如果一個變量要存儲某個值,就需要先聲明是什么類型。
s 為一個 String 類型的變量,這一點是毫無疑問的,對吧?那肯定啊,二哥,你別廢話了,怎么可能有人懷疑這一點。
Java 有兩種類型,一種是基本類型,一種是引用類型。聲明為基本類型的變量存儲的是值,聲明為引用類型的變量存儲的是對象的引用,這一點想必大家也不懷疑吧。
就之前那行語句來說,String 是一個引用類型,值為 null,也就是說 s 這個變量什么也沒存儲,就好像一個窯洞里面什么人也沒住,一樣。
假如 String s = "沉默王二"
,就可以使用下面這幅圖來表示。

而 String s = null
的表現形式則是不同的,它沒有可引用的對象。

那內存中的 null 到底是什么玩意呢?Java 中的 null 到底是什么樣的一個值?
不管怎么樣,null 不是一個有效的對象,所以內存中並沒有為它分配空間,沒它的位置。null 僅僅是一種表現符號,表明引用此時沒有指向任何一個對象。Java虛擬機的規范中也沒有規定 null 的具體值。
這不僅讓我聯想到了佛經中的一句經典台詞,想必大家也猜到了,大聲的念出來吧!“色即是空,空即是色。”我們漢語中的這個“空”字恰到好處地匹配了這個 null
,換句話說就是“null 即是空,空即是 null。”經典啊,經典。
一個類的成員變量如果是引用類型的話,它的默認值就為 null,這和基本類型有所不同。
public class Null {
private String name;
private int age;
public static void main(String[] args) {
Null n = new Null();
System.out.println(n.name);
System.out.println(n.age);
}
}
上面這段代碼的輸出結果是:
null
0
但如果是一個引用類型的局部變量的話,編譯器會提醒我們對其初始化。

如果一個變量當前沒有確定要初始化的值,那么 null 就是最佳選擇,即所謂的延遲初始化,直到實際使用的時候再賦值為“它實際”的值(null 的第 1 個好處)。更神奇的是,null 竟然可以被強制轉化。
String s = (String) null;
Integer i = (Integer) null;
System.out.println(s);
System.out.println(i);
程序不僅可以編譯通過,運行時也沒有拋出可怕的 NPE。但是呢,Java 還是有原則的,當把 null 賦值給基本類型變量的時候就會編譯不通過。

編譯器是不是很智能,很人性化,畢竟基本類型和引用類型是不同的,null 只能作為引用類型的初始化值,卻不能作為基本類型的初始化值,因為基本類型有自己的初始化值,比如說 int 的為 0。不過,不要太輕信人工智能化,我保證能把編譯器繞暈。
Integer j = null;
int k = j;
System.out.println(k);
先給基本類型的包裝類型變量賦值為 null,再把該變量賦值給基本類型變量,編譯器就無能為力了。不過,NPE 會在運行時被揪出來鞭屍了。
關於 null,還有另外一個有趣的事實:如果使用了帶有 null 值的引用類型變量,instanceof 將會返回 false。
String s1 = null;
System.out.println(s1 instanceof String); // false
也就是說,如果一個引用類型變量的值不為 null,並且在使用 instanceof 操作符判斷類型的時候沒有拋出 ClassCastException
,那么結果就為 true。否則,即便是為一個變量明確地聲明了類型,比如說 String s1 = null
,instanceof 仍然無法知道 s1 是 String 類型。
好了,我們再來看一看 null 的其他用途,比如說表示對象不存在、終止條件。
下圖是 System.console()
方法的 Javadoc,該方法會返回與當前 Java 虛擬機相關聯的唯一對象(如果有的話);如果沒有的話,返回 null。

想想看,如果 Java 沒有保留 null 的話,要返回什么呢?至少得再定義一個和 null 差不多意義的關鍵字。
再來看看另外一個例子,來自 java.io.BufferedReader
類的 readLine()
方法
public String readLine() throws IOException
Returns: A String containing the contents of the line, not including any line-termination characters, or null if the end of the stream has been reached.
該方法會一行一行地返回讀取的字符串,直到流的結尾。怎么判斷到了流的結尾呢,返回 null。這樣的話,我們就可以把判 null 作為讀取字符串的條件。
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
當然了,也可以返回其他的關鍵字,比如說 -1,來表示 readLine()
到了流的末尾,但這樣的做法和返回 null 沒有多大的分別。
最后,奉上一句羅素同學的名言:“存在即合理。”null 既然已經存在了,自然有它存在的道理,不管它的功過是非。
好了,各位讀者朋友們,以上就是本文的全部內容了。能看到這里的都是最優秀的程序員,升職加薪就是你了👍。原創不易,如果覺得有點用的話,請不要吝嗇你手中點贊的權力。
PS:當然還有更多好看的 Java 原創文章,比較多,這里不便一一列舉,感興趣的可以到公眾號【沉默王二】回復【null】獲取。