15

来吧,一文彻底搞懂Java中最特殊的存在——null

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzUyOTk5NDQwOA%3D%3D&%3Bmid=2247487517&%3Bidx=3&%3Bsn=dac5929a7a1afdfcdf7d64cc1fed50e3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

jYBBZjn.jpg!web

没事的时候,我并不喜欢逛 P 站,而喜欢逛 programcreek 这些技术型网站,于是那天晚上,在夜深人静的时候,我就发现了一个专注基础但不容忽视的主题。比如说: Java 中的 null 到底是什么鬼 ?像这类灵魂拷问的主题,非常值得深入地研究一下。

jURRBzb.png!web

null 在 Java 中是一个特殊的存在,因为它和 大名鼎鼎的 NullPointerException (NPE)如影随形。NPE 的发明人 Tony Hoare 曾在 2009 年承认:“Null References 是一个荒唐的设计,就好像我赌输掉了十亿美元”。

那为什么 Java 会一直保留着 null,而没有把它消灭掉呢?我想是因为 null 的存在的确为 Java 带来了更多好处(我在下文中指出了一些,看大家能不能发现哦)。

nmU7jqQ.jpg!web

就让我们从下面这个语句开始。

String s = null;

首先,我们来回顾一下:什么是变量,什么是值。举个恰当的例子,变量就好像一个窑洞,里面可以住人,也可以不住人,人就相当于值。在 Java 中,如果一个变量要存储某个值,就需要先声明是什么类型。

s 为一个 String 类型的变量,这一点是毫无疑问的,对吧?那肯定啊,二哥,你别废话了,怎么可能有人怀疑这一点。

Java 有两种类型,一种是基本类型,一种是引用类型。声明为基本类型的变量存储的是值,声明为引用类型的变量存储的是对象的引用,这一点想必大家也不怀疑吧。

就之前那行语句来说,String 是一个引用类型,值为 null,也就是说 s 这个变量什么也没存储,就好像一个窑洞里面什么人也没住,一样。

假如 String s = "沉默王二" ,就可以使用下面这幅图来表示。

IbyqiiM.png!web

String s = null 的表现形式则是不同的,它没有可引用的对象。

IZFr2aQ.png!web

内存中的 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

但如果是一个引用类型的局部变量的话,编译器会提醒我们对其初始化。

AjeuIfa.png!web

如果一个变量当前没有确定要初始化的值,那么 null 就是最佳选择,即所谓的延迟初始化,直到实际使用的时候再赋值为“它实际”的值( null 的第 1 个好处 )。更神奇的是, null 竟然可以被强制转化

String s = (String) null;
Integer i = (Integer) null;

System.out.println(s);
System.out.println(i);

程序不仅可以编译通过,运行时也没有抛出可怕的 NPE。但是呢,Java 还是有原则的,当把 null 赋值给基本类型变量的时候就会编译不通过。

NNJnIvu.png!web

编译器是不是很智能,很人性化,毕竟基本类型和引用类型是不同的,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。

F3Y7riR.png!web

想想看,如果 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 既然已经存在了,自然有它存在的道理,不管它的功过是非。

好了,各位读者朋友们,以上就是本文的全部内容了。 能看到这里的都是最优秀的程序员,升职加薪就是你了 :+1:。如果觉得不过瘾,还想看到更多,我再推荐几篇给大家。

关注微信公众号【程序员黄小斜】回复“2019”领取我这两年整理的学习资料

涵盖自学编程、求职面试、Java技术、算法刷题、计算机基础和考研等8000G资料合集。

A77ZruA.jpg!web

EvQfeuI.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK