关于java中的Null

关键字Null

Java中,Null是一个很有“意思”的关键字,设计之初用来标识一个不确定的对象或者一个缺失的对象。同时Null作为java中的关键字,如同public,static等等是大小写敏感的。但是Null说到底还是一个值,可以赋给任何引用变量但是不能将Null赋值给java的8种基本数据类型变量。如果在定义了变量类型之后,没有给变量赋值初始化,那么Null关键字是所有引用类型变量的默认初始值。在JVM的视线内,值为Null的引用变量是不会再被任何对象引用了,该变量将会被垃圾回收,从而释放内存。

a billion-dollar mistake

java语言的设计者曾经说过Null关键字的使用是billion-dollar mistake,为什么这么说?如果我们只是用Null作为一个值来传递是不会有任何问题的。可是一旦通过值为Null的引用变量来访问其引用对象(解引用操作)就会出问题,也就是我们时常会在写代码的时候遇到的空指针异常(java.lang.NullPointerException)
在编程的时候如果对Null不在意,甚至随意使用Null,后果是非常严重的。

NullPointerException

参考RednaxelaFX在知乎上的关于return Null是否安全的回答

在java中,对Null引用变量解引用就会抛出NPE异常。哪些操作包含了解引用呢?

  • 读字段(字节码 getfield):x.y,当x为null时抛NPE;
  • 写字段(字节码 putfield):x.y = z,当x为null时抛NPE。注意:z的值是什么没关系;
  • 读数组长度(字节码 arraylength):a.length,当a为null时抛NPE;
  • 读数组元素(字节码 <x>aload,<x>为类型前缀):a[i],当a为null时抛NPE;
  • 写数组元素(字节码 <x>astore,<x>为类型前缀):a[i] = x,当a为null时抛NPE。注意:x的值时什么没关系;
  • 调用成员方法(字节码 invokevirtual、invokeinterface、invokespecial):obj.foo(x, y, z),当obj为null时抛NPE。注意:参数的值是什么没关系;
  • 增强for循环(也叫foreach循环):
    • 对数组时(实际隐含a.length操作):for (E e : a) { … } , 当a为null时抛NPE;
    • 对Iterable时(实际隐含对Iterable.iterator()的调用):for (E e : es) { … } ,当es为null时抛NPE;
  • 自动拆箱(实际隐含 <XXX>.<xxx>Value() 的调用,<XXX>为包装类型名,<xxx>为对应的原始类型名): (int) integerObj,当integerObj为null时抛NPE;
  • 对String做switch(实际隐含的操作包含对String.hashCode()的调用):switch (s) { case “abc”: … } ,当s为null时抛NPE;
  • 创建内部类对象实例(字节码 new,但这里特指创建内部类实例的情况):outer.new Inner(x, y, z),当outer为null时抛NPE;
  • 抛异常(字节码 athrow):throw obj,当obj(throw表达式的参数)为null时抛NPE;
  • 用synchronized关键字给对象加锁(字节码 monitorenter / monitorexit):synchronized (obj) { … },当obj为null时抛NPE。

Avoid Nulls

尽量避免使用空值。不要返回 Null 的集合,可以选择返回一个 empty 的集合。

如果你确实准备使用 Null,可以考虑使用 @Nullable注解。IntelliJ IDEA 内置支持 @Nullable 注解。

注解@Nullable和@NotNull的作用:用来标注方法是否能传入null值,如果可以传入Null值,则标记为Nullbale,如果不可以则标注为NotNull. 在我们做了一些不安全严谨的编码操作的时候,这些注释会给我们一些警告。

如果使用了Java 8,推荐优秀的 Optional 类型来包裹Null引用。
使用 Optional 唯一不好的是标准库对 Optional 的支持并不是很好,所以对 null 的处理仍然是必要的。

关于Optional的介绍

0%