你真的了解“null”吗?

405 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

问题情境:

String a = null;
if(a.equals(null)||a == null ){
    return true;
}else{
    return false;   
}

请问你的答案是什么?

true?还是false?我想告诉你,都不是!这里会报空指针异常!即你会看到这个异常:java.lang.NullPointerException。

why?为啥嘞?

这里我先不告诉你为什么。我先要来介绍一下java里面的null到底是个什么东东。主要以几个关键点来介绍。

1、null是一个关键字

首先,null是一个关键字,它和java里面的public、static、void等一样都是关键字。java中关键字对大小写是敏感的,所以null和Null不一样。要注意了。

2、null是任何引用类型的默认值

​ java中任何类型都有一个默认值,基本类型有各自的默认值,例如:你定义了一个int i,那么系统会自动帮你给这个i附一个这个类型下的默认值,int类型的默认值是0,boolean类型的默认值是false,而引用类型的默认值则是null,或者说所有Object类型及其子类型的默认值都是null。

这里你可能会说,java1.5之后对基本类型都有一个自动装箱、自动拆箱的过程啊,那int自动装箱后就是Integer引用类型了,那它的默认值就是null了,这里面不就有问题了吗?

是的,这里就有问题了,问题在哪里?这里卖个关子,我们后面说。

我们继续说对于任何引用变量的默认值都是null,它对java中的所有引用变量都是用,如,成员变量、局部变量、实例变量、静态变量等等。(这里要注意,当你使用一个没有赋值的局部变量是,编译器会给你一个错误警告,并且不会让你通过。)下面我们测试一下吧。

public class test {
private static String a2;//静态变量
    public static void main(String[] args) {
        String a3 ;//编译器会警告//局部变量
        Cat cat = new Cat();
        System.out.println("成员变量 a1="+cat.a1);
        System.out.println("静态变量 a2="+a2);
        //System.out.println("静态变量 a3="+a3);编译器不通过
        }
    }
 
class Cat{
	public Integer a1;//成员变量
}

显示的结果:

成员变量 a1=null
静态变量 a2=null

3、null是一个特殊的值

​ 前面说null是一个关键字,它既不是应用类型,也不是基本类型,它是一个比较特殊的值。你可以将null赋值给引用类型,你也可以将其转化为任何引用类型。例如:

String b1 = null;
Integer b2 = null;
Double b3 = null;
String c1 = (String)null;
Integer c2 = (Integer)null;
Double c3 = (Double)null;
System.out.println("b1 = "+b1);
System.out.println("b2 = "+b2);
System.out.println("b3 = "+b3);
System.out.println("c1 = "+c1);
System.out.println("c2 = "+c2);
System.out.println("c3 = "+c3);

​ 编译通过,运行结果如下:

b1 = null
b2 = null
b3 = null
c1 = null
c2 = null
c3 = null

​ 但是你不能将null赋值给一个基本类型。例如你在进行如下操作的时候,编译器不会让你通过编译:

int i = null;
double d = null;
boolean b = null;
char c = null;
short t = null;
long l = null;
float f = null;
byte bt =null;

​ 以上语句编译器会报错,通过不了。既然不能将null直接赋值给基本类型数据,那么我可以先将null赋值给基本类型的包装类型,再赋值给基本类型可以吗?试试吧:

int aa1 = (Integer) null;
double aa2 = (Double) null;
boolean aa3 = (Boolean) null;
char aa4 = (Character) null;
short aa5 = (Short) null;
long aa6 = (Long) null;
float aa7 = (Float) null;
byte aa8 =(Byte) null;

System.out.println(aa1);
System.out.println(aa2);
System.out.println(aa3);
System.out.println(aa4);
System.out.println(aa5);
System.out.println(aa6);
System.out.println(aa7);
System.out.println(aa8);

​ 可以看到当你将null利用包装类型包装后再赋值给基本类型时,编译是通过的,但是但你运行时,就汇报一下错误:

java.lang.NullPointerException

​ 这就是空指针异常。为什么编译能通过,运行确报错呢?

这里关系到编译器机制问题了,我就不详细说了,通常,编译器只是对你的代码格式等问题进行检查,当它发现你直接将null赋值给基本类型性,编译器检查到这个格式是不正确的,所以报错,不通过编译;

但是当我们将null包装后,就成为了包装类型,这时就是等号左边是基本类型,右边是包装类型,而编译器在编译时会自动将基本类型装箱为Integer类型,所以两边都是Object类型,所以编辑器就让通过了。而运行时,比较的是真正的类型比较,也就是说,运行的时候,系统会将赋值为null的包装类型自动的有拆箱为基本类型,也就是讲null暴露出来了,那么这时,就不符合“null不能复制给基本类型的规则了”,所以就会报出一个空指针异常。

4、可以使用instanceof操作来判断null是否为引用类型

​ 进行如下操作:

Integer in = null;
        if(in  instanceof Integer) {
    System.out.println("true");
}else {
    System.out.println("false");
}

​ 结果是:

false

​ 这就充分证明了null不是引用类型。所以我们以后可以通过instanceof操作判断一个变量的真正类型。

5、null在使用==和!=的情况

我们可以使用==或者!=来比较左右两边包含null的情况,例如:

Object o = null;
if(null == "") {
System.out.println("true");
}else {
System.out.println("false");
}

运行结果为:

false

但是我们不能使用其他方法来比较左右两边包含null的情况,例如

Object object = null;
if(object.equals("")) {
System.out.println("true");
}else {
System.out.println("false");
}

​ 运行报空指针异常,结果如下:

java.lang.NullPointerException

​ 这就解释了文章一开始设计的那个案例了。

​ 这就是报错的核心。我们再来看一下问题的代码:

String a = null;
if(a.equals(null) || a == null ){
    return true;
}else{
    return false;   
}

​ 当我们运行该程序时,将null赋值给String类型,然后进入if判断,里面有两个判断条件,一个是a.equals(null),另一个是a==null。这里要注意:逻辑运算符||在进行判断时,只要有一个条件为true结果就为true。但是当我们进行if判断时,会执行a.equals(null),这时就会报出空指针异常的错误了,因为我们说了,null不能用其他方法进行比较,只能用==(!=)来比较。

​ 这就是这个问题的整个运行过程。