在我们学习和使用Guava的Optional之前,我们需要来了解一下Java中null。因为,只有我们深入的了解了null的相关知识,我们才能更加深入体会领悟到Guava的Optional设计和使用上的优雅和简单。
NullPointerException,大家应该都见过。这是Tony Hoare在设计ALGOL W语言时提出的null引用的想法,他的设计初衷是想通过编译器的自动检测机制,确保所有使用引用的地方都是绝对安全的。很多年后,他对自己曾经做过的这个决定而后悔不已,把它称为“我价值百万的重大失误”。它带来的后果就是---我们想判断一个对象中的某个字段进行检查,结果发现我们查看的不是一个对象,而是一个空指针,他会立即抛出NullPointerException异常。
Java中null的使用有时候会产生一些意想不到的内伤:
1.无法表达具体的业务含义,语义含糊不清;
2.增加了NullPointException的发生,因为不知道什么地方就返回了一个null;
3.null和空容易混为一谈;
4.需要非null判断,弱可读性、代码不够优雅。返回一个null值绝对不是一个好的选择,所以,对于null关键字尽量避免使用。
空指针是我们最常见也最讨厌的异常,为了防止空指针异常,你不得在代码里写大量的非空判断。
null值是一种令人不满的模糊含义。有的时候会产生二义性,这时候我们就很难搞清楚具体的意思,如果程序返回一个null值,其代表的含义到底是什么,例如:Map.get(key)若返回value值为null,其代表的含义可能是该键指向的value值是null,亦或者该键在map中并不存在。null值可以表示失败,可以表示成功,几乎可以表示任何情况。用其它一些值(而不是null值)可以让你的代码表述的含义更清晰。
ConcurrentHashMap不允许为空
The main reason that nulls aren’t allowed in ConcurrentMaps
(ConcurrentHashMaps, ConcurrentSkipListMaps) is that
ambiguities that may be just barely tolerable in non-concurrent
maps can’t be accommodated. The main one is that if
map.get(key) returns null, you can’t detect whether the
key explicitly maps to null vs the key isn’t mapped.
In a non-concurrent map, you can check this via map.contains(key),
but in a concurrent one, the map might have changed between calls.
Guava文档中,第一篇就提到的尽量避免使用Null,会给代码带来一些负面影响,并举出map.get(key) == null,带来的混淆。由此。Guava提出了Optional的概念。
Guava用Optional表示可能为null的T类型引用。一个Optional实例可能包含非null的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是null值,而是用存在或缺失来表示。但Optional从不会包含null值引用。
Guava的Optional有两种实现,Absent和Present,这就可以理解为,传统代码书写方式中的null和non-null。而Guava中Absent和Present中重写Optional的isPresent()方法。
类声明
@GwtCompatible(serializable = true)
public abstract class Optional<T> implements Serializable
Optional.of(T)
public void test1(){
Integer num = null;
Optional<Integer> op1 = Optional.of(num); // java.lang.NullPointerException
System.out.println(op1.get());
}
上面的程序,我们使用Optional.of(null)方法,这时候程序会第一时间抛出空指针异常,这可以帮助我们尽早发现问题。如果给定值不为null,则会返回给定值的Optional实例。
public static <T> Optional<T> of(T reference) {
return new Present<T>(checkNotNull(reference));
}
首先使用checkNotNull来判断给定值是否为null,如果为null,则会抛出空指针异常,否则返回给定值的Optional的实例(Present是Optional的子类)。
Optional.absent()
public void test3(){
Integer num = new Integer(4);
Optional<Integer> op = Optional.absent();
Optional<Integer> op2 = Optional.of(num);
System.out.println("op:" + op.isPresent() + " op2:" + op2.isPresent());
}
上面的程序,我们使用Optional.absent()方法,创建引用缺失的Optional实例。 源码:
public static <T> Optional<T> absent() {
return Absent.withType();
}
static final Absent<Object> INSTANCE = new Absent<Object>();
@SuppressWarnings("unchecked") // implementation is "fully variant"
static <T> Optional<T> withType() {
return (Optional<T>) INSTANCE;
}
通过withType方法返回一个静态Absent对象,并强制转换为Optional对象。从上面就可以看出其中不包含任何的引用。
Optional.fromNullable(T)
创建指定引用的Optional实例,若引用为null则表示缺失,返回应用缺失对象Absent,否则返回引用存在对象Present。
public void test4(){
Integer num1 = null;
Integer num2 = new Integer(4);
Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op1:" + op1.isPresent() + " op2:" + op2.isPresent()); // false true
}
}
public static <T> Optional<T> fromNullable(@Nullable T nullableReference) {
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
}
从上面源码中可以看出如果T为null,则调用Optional静态方法absent(),表示引用缺失;如果T不为null,则创建一个Present对象,表示引用存在。
T get()
返回Optional包含的T实例,该T实例必须不为空;否则,对包含null的Optional实例调用get()会抛出一个IllegalStateException异常。
public void test5(){
Integer num1 = null;
Integer num2 = new Integer(4);
Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.get()); // 4
System.out.println("op1:" + op1.get()); // java.lang.IllegalStateException: Optional.get() cannot be called on an absent value
}
因为fromNullable对象根据给定值是否为null,返回不同的对象:
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
因此调用的get方法也将会不一样。
public abstract T get();
如果返回的是一个Present对象,将调用Present类中的get()方法:
@Override
public T get() {
return reference;
}
如果返回的是一个Absent对象,将调用Absent类中的get()方法:
@Override
public T get() {
throw new IllegalStateException("Optional.get() cannot be called on an absent value");
}
T or (T)
返回Optional所包含的引用,若引用缺失,返回指定的值。
public void test6(){
Integer num1 = null;
Integer num2 = new Integer(4);
Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.or(0)); // 4
System.out.println("op1:" + op1.or(0)); // 0
}
因为fromNullable对象根据给定值是否为null,返回不同的对象:
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
因此调用的or方法也将会不一样。
public abstract T or(T defaultValue);
如果返回的是一个Present对象,将调用Present类中的or()方法:
@Override
public T or(T defaultValue) {
checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
return reference;
}
这个方法首先对默认值进行判断,如果不为null,则返回引用;如果为null,抛出空指针异常,这种情况可以使用Optional.orNull()方法代替。
(2)如果返回的是一个Absent对象,将调用Absent类中的or()方法:
@Override
public T or(T defaultValue) {
return checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
}
这个方法首先对默认值进行判断,如果不为null,则返回默认值;如果为null,抛出空指针异常,这种情况可以使用Optional.orNull()方法代替。
public void test6(){
String num1 = null;
String num2 = "123";
String defaultNum = null;
Optional<String> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<String> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.or("0")); // 123
System.out.println("op1:" + op1.or(defaultNum)); // java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
}
T orNull()
返回Optional所包含的引用,若引用缺失,返回null
public void test6(){
String num1 = null;
String num2 = "123";
Optional<String> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<String> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.orNull()); // 123
System.out.println("op1:" + op1.orNull()); // null
}
因为fromNullable对象根据给定值是否为null,返回不同的对象:
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
因此调用的orNull方法也将会不一样。
@Nullable
public abstract T orNull();
(1)如果返回的是一个Present对象,将调用Present类中的orNull()方法:
@Override
public T orNull() {
return reference;
}
引用存在,返回引用。
(2)如果返回的是一个Absent对象,将调用Absent类中的orNull()方法:
@Override
@Nullable
public T orNull() {
return null;
}
引用缺失,返回null,此时没有默认值。
注意事项
不要在Set中使用null,或者把null作为map的键值。使用特殊值代表null会让查找操作的语义更清晰。
如果你想把null作为map中某条目的值,更好的办法是 不把这一条目放到map中,而是单独维护一个”值为null的键集合” (null keys)。Map 中对应某个键的值是null,和map中没有对应某个键的值,是非常容易混淆的两种情况。因此,最好把值为null的键分离开,并且仔细想想,null值的键在你的项目中到底表达了什么语义。
使用Optional的意义在哪儿?
- Optional 迫使你积极思考引用缺失的情况 因为你必须显式地从Optional获取引用。
- 如同输入参数,方法的返回值也可能是null。和其他人一样,你绝对很可能会忘记别人写的方法method(a,b)会返回一个null,就好像当你实现method(a,b)时,也很可能忘记输入参数a可以为null。将方法的返回类型指定为Optional,方法的参数设置为Optional,也可以迫使调用者思考返回的引用缺失的情形。
public static Optional<Integer> sum(Optional<Integer> a,Optional<Integer> b){
if(a.isPresent() && b.isPresent()){
return Optional.of(a.get()+b.get());
}
return Optional.absent();
}
java8 optional实战
以前写法
public String getCity(User user) throws Exception{
if(user!=null){
if(user.getAddress()!=null){
Address address = user.getAddress();
if(address.getCity()!=null){
return address.getCity();
}
}
}
throw new Excpetion("取值错误");
}
JAVA8写法 (java8Optional就是受guava Opitonal启发)
public String getCity(User user) throws Exception{
return Optional.ofNullable(user)
.map(u-> u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->new Exception("取指错误"));
}
以前写法
if(user!=null){
dosomething(user);
}
JAVA8写法
Optional.ofNullable(user)
.ifPresent(u->{
dosomething(u);
});
以前写法
public User getUser(User user) throws Exception{
if(user!=null){
String name = user.getName();
if("zhangsan".equals(name)){
return user;
}
}else{
user = new User();
user.setName("zhangsan");
return user;
}
}
现在写法
public User getUser(User user) {
return Optional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()-> {
User user1 = new User();
user1.setName("zhangsan");
return user1;
});
}