Guava之Optional原理

在我们学习和使用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值引用。

image.png

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;
                   });
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容