JAVA中的null
null是Java中一个很重要的概念。null设计初衷是为了表示一些不确定的东西。null是Java中的关键字,它是大小写敏感的,你不能将null写成Null或NULL,编译器将不能识别它们然后报错。java中null是任何引用类型的默认值,不严格的说是所有object类型的默认值。Java中的任何引用变量都将null作为默认值。null既不是对象也不是一种类型,它仅是一种特殊的值,你可以将其赋予任何引用类型,你也可以将null转化成任何类型。
String str = null; // null can be assigned to String String myStr = (String) null; // null can be type cast to String
你可以看到在编译和运行时期,将null强制转换成任何引用类型都是可行的,在运行时期都不会抛出空指针异常。null可以赋值给引用变量,但不能将null赋给基本类型变量。如果你那样做了,编译器将会报错。但是如果将null赋值给包装类object,然后将object赋给各自的基本类型,编译器不会报,但是你将会在运行时期遇到空指针异常。这是Java中的自动拆箱导致的,我们将在下一个要点看到它。任何含有null值的包装类在Java拆箱生成基本数据类型时候都会抛出一个空指针异常。一些程序员犯这样的错误,他们认为自动装箱会将null转换成各自基本类型的默认值,例如对于int转换成0,布尔类型转换成false,但是那是不正确的,如下面所示:
Integer iAmNull = null; int i = iAmNull; // Remember - No Compilation Error
但是当你运行上面的代码片段的时候,你会在控制台上看到主线程抛出空指针异常。在使用HashMap和Integer键值的时候会发生很多这样的错误。当你运行下面代码的时候就会出现错误。
如果使用了带有null值的引用类型变量,instanceof操作将会返回false:这是instanceof操作一个很重要的特性,使得对类型强制转换检查很有用
你可能知道不能调用非静态方法来使用一个值为null的引用类型变量。它将会抛出空指针异常,但是你可能不知道,你可以使用静态方法来使用一个值为null的引用类型变量。因为静态方法使用静态绑定,不会抛出空指针异常。下面是一个例子:
public class NullInstance { public static void canCalledByNullReference() { System.out.println("可以被NULL引用调用"); } public void cannotCalledByNullReference() { System.out.println("不可以被NULL引用调用"); } } public class NullDemo { public static void main(String[] args) { NullInstance instance=null; instance.canCalledByNullReference(); instance.cannotCalledByNullReference(); } }
输出:
Exception in thread "main" 可以被NULL引用调用 java.lang.NullPointerException at com.netease.NullDemo.main(NullDemo.java:7)
将null传递给方法使用,这时方法可以接收任何引用类型,例如public void print(Object obj)可以这样调用print(null)。从编译角度来看这是可以的,但结果完全取决于方法。Null安全的方法,如在这个例子中的print方法,不会抛出空指针异常,只是优雅的退出。如果业务逻辑允许的话,推荐使用null安全的方法。同时可以使用==或者!=操作来比较null值,但是不能使用其他算法或者逻辑操作,例如小于或者大于。
null对象的使用:
1.容器类型与null
- List:允许重复元素,可以加入任意多个null。
- Set:不允许重复元素,最多可以加入一个null。
- Map:Map的key最多可以加入一个null,value字段没有限制。
- 数组:基本类型数组,定义后,如果不给定初始值,则java运行时会自动给定值。引用类型数组,不给定初始值,则所有的元素值为null。
2.null的其他作用
- 判断一个引用类型数据是否null。 用==来判断。
- 释放内存,让一个非null的引用类型变量指向null。这样这个对象就不再被任何对象应用了。等待JVM垃圾回收机制去回收。
首先,对于null的随意使用会一系列难以预料的问题。通过对大量代码的研究和分析,我们发现大概95%以上的集合类默认并不接受null值,如果有null值将被放入集合中,代码会立刻中断并报错而不是默认存储null值,对于开发来说,这样能够更加容易的定位程序出错的地方。
另外,null值是一种令人不满的模糊含义。有的时候会产生二义性,这时候我们就很难搞清楚具体的意思,如果程序返回一个null值,其代表的含义到底是什么,例如:Map.get(key)若返回value值为null,其代表的含义可能是该键指向的value值是null,亦或者该键在map中并不存在。null值可以表示失败,可以表示成功,几乎可以表示任何情况。用其它一些值(而不是null值)可以让你的代码表述的含义更清晰。
反过来说,使用null值在有些情况下是一种正确的选择,因为从内存消耗和效率方面考虑,使用null更加廉价,而且在对象数组中出现null也是不可避免的。但是在程序代码中,比方说在函数库中,null值的使用会变成导致误解的元凶,也会导致一些莫名的,模糊的,很难修正的问题。
Guava的Optional
大多数情况下程序员使用null是为了表示某种不存在的意思,也许应该有一个value,但是这个value是空或者这个value找不到。比方说,在用不存在的key值从map中取value,Map.get返回null表示没有该map中不包含这个key。
若T类型数据可以为null,Optional<T>是用来以非空值替代T数据类型的一种方法。一个Optional对象可以包含一个非空的T引用(这种情况下我们称之为“存在的”)或者不包含任何东西(这种情况下我们称之为“空缺的”)。但Optional从来不会包含对null值的引用。
import com.google.common.base.Optional; public class OptionalTest { public void testOptional() throws Exception { Optional<Integer> possible=Optional.of(6); if(possible.isPresent()){ System.out.println("possible isPresent:"+possible.isPresent()); System.out.println("possible value:"+possible.get()); } } }
由于这些原因,Guava库设计了Optional来解决null的问题。许多Guava的工具被设计成如果有null值存在即刻报错而不是只要上下文接受处理null值就默认使用null值继续运行。而且,Guava提供了Optional等一些工具让你在不得不使用null值的时候,可以更加简便的使用null并帮助你避免直接使用null。
Optional<T>的最常用价值在于,例如,假设一个方法返回某一个数据类型,调用这个方法的代码来根据这个方法的返回值来做下一步的动作,若该方法可以返回一个null值表示成功,或者表示失败,在这里看来都是意义含糊的,所以使用Optional<T>作为返回值,则后续代码可以通过isPresent()来判断是否返回了期望的值(原本期望返回null或者返回不为null,其意义不清晰),并且可以使用get()来获得实际的返回值。Optional方法说明和使用实例:
1.常用静态方法:
- Optional.of(T):获得一个Optional对象,其内部包含了一个非null的T数据类型实例,若T=null,则立刻报错。
- Optional.absent():获得一个Optional对象,其内部包含了空值
- Optional.fromNullable(T):将一个T的实例转换为Optional对象,T的实例可以不为空,也可以为空[Optional.fromNullable(null),和Optional.absent()等价。
使用实例如下:
import com.google.common.base.Optional; public class OptionalTest { @Test public void testOptional() throws Exception { Optional<Integer> possible=Optional.of(6); Optional<Integer> absentOpt=Optional.absent(); Optional<Integer> NullableOpt=Optional.fromNullable(null); Optional<Integer> NoNullableOpt=Optional.fromNullable(10); if(possible.isPresent()){ System.out.println("possible isPresent:"+possible.isPresent()); System.out.println("possible value:"+possible.get()); } if(absentOpt.isPresent()){ System.out.println("absentOpt isPresent:"+absentOpt.isPresent()); ; } if(NullableOpt.isPresent()){ System.out.println("fromNullableOpt isPresent:"+NullableOpt.isPresent()); ; } if(NoNullableOpt.isPresent()){ System.out.println("NoNullableOpt isPresent:"+NoNullableOpt.isPresent()); ; } } }
2.实例方法:
- boolean isPresent():如果Optional包含的T实例不为null,则返回true;若T实例为null,返回false
- T get():返回Optional包含的T实例,该T实例必须不为空;否则,对包含null的Optional实例调用get()会抛出一个IllegalStateException异常
- T or(T):若Optional实例中包含了传入的T的相同实例,返回Optional包含的该T实例,否则返回输入的T实例作为默认值
- T orNull():返回Optional实例中包含的非空T实例,如果Optional中包含的是空值,返回null,逆操作是fromNullable()
- Set<T> asSet():返回一个不可修改的Set,该Set中包含Optional实例中包含的所有非空存在的T实例,且在该Set中,每个T实例都是单态,如果Optional中没有非空存在的T实例,返回的将是一个空的不可修改的Set。
使用实例如下:
import java.util.Set; import com.google.common.base.Optional; public class OptionalTest { public void testMethodReturn() { Optional<Long> value = method(); if(value.isPresent()==true){ System.out.println("获得返回值: " + value.get()); }else{ System.out.println("获得返回值: " + value.or(-12L)); } System.out.println("获得返回值 orNull: " + value.orNull()); Optional<Long> valueNoNull = methodNoNull(); if(valueNoNull.isPresent()==true){ Set<Long> set=valueNoNull.asSet(); System.out.println("获得返回值 set 的 size : " + set.size()); System.out.println("获得返回值: " + valueNoNull.get()); }else{ System.out.println("获得返回值: " + valueNoNull.or(-12L)); } System.out.println("获得返回值 orNull: " + valueNoNull.orNull()); } private Optional<Long> method() { return Optional.fromNullable(null); } private Optional<Long> methodNoNull() { return Optional.fromNullable(15L); } }
输出结果:
获得返回值: -12 获得返回值 orNull: null 获得返回值 set 的 size : 1 获得返回值: 15 获得返回值 orNull: 15
Optional除了给null值命名所带来的代码可阅读性的提高,最大的好处莫过于Optional是傻瓜式的。Optional对象的使用强迫你去积极的思考这样一种情况,如果你想让你的程序返回null值,这null值代表的含义是什么,因为你想要取得返回值,必然从Optional对象内部去获得,所以你必然会这么去思考。但是只是简单的使用一个Null值会很轻易的让人忘记去思索代码所要表达的含义到底是什么,尽管FindBugs有些帮助,但是我们还是认为它并没有尽可能的解决好帮助程序员去思索null值代表的含义这个问题。
这种思考会在你返回某些存在的值或者不存在的值的时候显得特别相关。和其他人一样,你绝对很可能会忘记别人写的方法method(a,b)可能会返回一个null值,就好像当你去写method(a,b)的实现时,你也很可能忘记输入参数a也可以是null。如果返回的是Optional对象,对于调用者来说,就可以忘却怎么去度量null代表的是什么含义,因为他们始终要从optional对象中去获得真正的返回值。