前面讲过Java中Null表示空对象时带来NullPointerException的问题,以及解决这些问题的几种办法。其中之一就是使用Optional,今天我们要来了解更多的Optional的使用方法。
从下面这段简单的代码开始:
public String getName() { Company company = getCompany(); if (company != null) { if (company.founder != null) { return company.founder.name; } else return null; } else return null; }代码的意图是从一个Company实例中取出founder的name。 这样简单的逻辑,却用 If 嵌套来防止Null带来的NullPointerException,显然啰嗦,但似乎也无可奈何了。当然你会改写一下程序来减少代码行数:
public String getName() { Company company = getCompany(); return company != null ? company.founder != null ? company.founder.name : null : null; }上面的代码看起来已经没有什么可以挑剔的了,但是我们会发现一半的逻辑在做与方法无关Null的判断,而真正的逻辑却被淹没在这些副作用当中。我们现在就用Optional的一些技巧来移除Null判断,让程序只关心真正的逻辑。
所以,首先要让getCompany()方法返回一个Optional<Company> company, 然后根据步骤2,给company绑定一个提取founder的操作,并且把founder包装成一个Optional<Founder>,为此我们需要一个绑定操作到某个可空对象的方法:
public class Optionals { static <V, R> Optional<R> bind(Optional<V> optional, Function<V, R> function) { if (optional.isPresent()) return Optional.ofNullable(function.apply(optional.get())); else return Optional.empty(); } }
Optional<String> name= bind(bind(getCompany(), c -> c.founder), f -> f.name);
怎么样代码看起更少了,也彻底甩掉了Null这个累赘。在看看这个递归调用,很熟悉不是吗?多像Scheme中写Labmda 表达式!这是纯函数式编程的风格,但对于Java来说,我们希望这个绑定function的操作能在放在对象上来,而不需要借助Optionals.bind这样一个第三者。所以我们想办法给我们Optional<V> 对象添加绑定操作的功能,这个简单,我们很容易想到各种增加功能的设计模式:你可以直接继承Optional<V>或者用一个新的类型来装饰Optional<V>, 这里我们就用Decorator 模式来实现:
public class StreamOptional<V> extends AbstractStreamOptional<V> { public StreamOptional(Optional<V> optional) { super(optional); } static <R> StreamOptional<R> from(Optional<R> optional) { return new StreamOptional(optional); } public <R> StreamOptional<R> bind(Function<V, R> function) { if (this.isPresent()) { return StreamOptional.from(Optional.<R>ofNullable(function.apply(this.get()))); } else return StreamOptional.from(Optional.<R>empty()); }然后我们再来实现上面例子,代码就变成这样:
StreamOptional<String> name = from(getCompany()).bind(company -> company.founder).bind(founder -> founder.name);
总结: 在对可空对象的处理时,为避免做无关的事情,绑定操作到这个对象上,这种做法其实来源了Monad模型,在一个数据上赋予一系列的计算过程的,可以像管道一样处理数据。无论是函数风格还是链式调用,都体现到了这一点。