原文地址:并不是Null Object这么简单
博客地址:zhangyi.farbox.comjava
在大多数程序语言中,咱们都须要与Null打交道,而且纠缠于对它的检查中。一不当心让它给溜出来,就可能像打开潘多拉的盒子通常,给程序世界带来灾难。提及来,在咱们人类世界中,Null到底算什么“东西”呢?语义上讲,它就是一场空,即所谓“虚无”。这个世界并无任何物质能够表明“虚无”,于是它仅存于咱们的精神层面。说虚无存在实际上是一种悖论,由于存在实际上是虚无的反面。若从程序本质上讲,Null表明一种状态,指一个对象(或变量),虽获声明却未真正诞生,甚至可能永远不会诞生。而一旦诞生,Null就被抹去了,回归了正确的状态。编程
站在OO的角度来说,既然Everything is object,天然能够将Null一样视为Object——这近似于前面提到的悖论,既然是Null,为什么又是Object呢?换言之,在对象世界里,其实没有什么不存在,所谓“不存在”仍然是一种“存在”。这么说容易让人变糊涂,就好像咱们搞不清楚“我是谁”。因此,我宁可采用Martin Fowler的说法,将Null Object视为一种Special Case,即Null实际上是一种特例。app
视Null为一种特例,便可用OO的特化来表达。当某个对象可能存在Null这种状态时,均可以将这种状态表示为一种特化的类,它再也不表明Null,而是表明“什么都不作”。凡是返回Null的地方,都替换为这个Null Object,用以表达这种Null其实仅仅是一种特列。因而乎,咱们像抹杀异教徒通常抹去了“虚无”的存在。(当虚无被抹去,是什么样的存在?)框架
然而,若在程序语言中实现本身的Null Object,当然能够在必定程度上消除对Null的检查,却存在一些约束:ide
Google的Guava框架为了解决这一问题,引入了Optional
public abstract class Optional<T> implements Serializable {
public static <T> Optional<T> absent() {
return (Optional<T>) Absent.INSTANCE;
}
public static <T> Optional<T> of(T reference) {
return new Present<T>(checkNotNull(reference));
}
public static <T> Optional<T> fromNullable(@Nullable T nullableReference) {
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
}
public abstract boolean isPresent();
public abstract T get();
public abstract T or(T defaultValue);
public abstract <V> Optional<V> transform(Function<? super T, V> function);
}复制代码
因而,咱们能够这样来使用Optional
public final Optional<E> first() {
Iterator<E> iterator = iterable.iterator();
return iterator.hasNext()
? Optional.of(iterator.next())
: Optional.<E>absent();
}复制代码
first()方法返回的是一个Optional
List<Person> persons = newArrayList();
String name = from(persons).first().transform(new Function<Person, String>() {
@Override
public String apply(Person input) {
return input.getName();
}
}).or("not found");
assertThat(name, is("not found"));复制代码
不知是巧合,仍是一种借鉴,Java 8一样定义了Optional用以处理这种状况。前面的代码在Java 8下能够改写为:this
List<Person> persons = newArrayList();
String name = persons.stream().findFirst().map(p -> p.getName()).orElse("not found");
assertThat(name, is("not found"));复制代码
其实在Scala的早期版本,已经提供了Option[T]类型。前面的代码若用scala编写,就变成:spa
case class Person(name: String, age: Int)
val persons = List[Person]()
persons.headOption.map(p => p.name).getOrElse("not found")复制代码
这样的设计方式,仍是Null Object模式吗?让咱们回到Null的本原状态,思考为何会产生Null?首先,Null表明一种异常状态,即在某种未可知的情形下,可能返回Null;正常状况下,返回的则是非Null的对象。Null与非Null,表明一种未知与不肯定性。哈姆雷特纠结于“To be, or not to be, this is a question”,但在程序世界里,能够抽象为一个集合来表达这种非此即彼的情况。
从函数式编程的角度来说,咱们能够将这样的集合设计为一个Monad。根据DSL in Action一书中对Monad的介绍,一个Monad由如下三部分定义:
一个抽象M[A],其中M是类型构造函数。在Scala语言中能够写成class M[A],或者case class M[A],有或者trait M[A]
一个unit方法(unit v)。对应Scala中的函数new M(v)或者M(v)的调用。
一个bind方法,起到将运算排成序列的做用。在Scala中经过flatMap组合子来实现。bind f m对应的Scala语句是m flatMap f。
同时,Monad还必须知足如下三条规则:
右单位元(identity)。即对于任意Monad m,有m flatMap unit => m。对于Option,unit就是Option伴生对象定义的apply()方法。若m为Some("Scala"),则m flatMap {x => Option(x)},其结果仍是m。
左单位元(unit)。即对于任意Monad m,有unit(v) flatMap f => f(v)。
假设咱们定义一个函数f:
def f(v: String) = Option(v)复制代码
则Option("Scala") flatMap {x => f(x)}的结果就等于f("scala")。
结合律。即对于任意Monad m,有m flatMap g flatMap h => m flatMap {x => g(x) flatMap h}。
不管是Scala中的Option[A],仍是Java 8中的Optional[T],都是一个Monad。此时的Null再也不是特例,而是抽象Option[A]对称的两个元素中的其中一个,在Scala中,即Option[T]中的Some[T]或None。它们俩面貌相同,倒是一对性格迥异的双生子。
在设计为Monad后,就能够利用Monad提供的bind功能,完成多个函数的组合。组合时,并不须要考虑返回为None的状况。Monad能保证在前一个函数返回空值时,后续函数不会被调用。让咱们来看一个案例。例如,咱们须要根据某个key从会话中得到对应的值,而后再将该值做为参数去查询符合条件的特定Customer。在Scala中,能够将这两个步骤定义为函数,返回结果分别为Option[String]与Option[Customer]:
def params(key: String): Option[String]
def queryCustomer(refId: String): Option[Customer]
val customer =
(
for {
r <- params("customerId")
c <- queryCustomer(r)
} yield c
) getOrElse error("Not Found")复制代码
这段代码用到了Scala的for comprehension,它实则是对flatMap的一种包装。尤为当嵌套多个flatMap时,使用for comprehension会更加直观可读。翻译为flatMap,则为:
params("customerId").flatMap {
r => queryCustomer(r).map {
c => c
}
} getOrElse error("Not Found")复制代码
当我最初看到Guava设计的Optional[T]时,我觉得是Null Object模式的体现。显然,它的功能要超出Null Object的范畴。但它也并不是Monad,在前面给出的定义中,咱们能够看到Guava的Optional[T]仅提供了map(即定义中的transform)功能,而没有提供更基本的flatMap操做。具备函数式编程功能的Scala与Java 8增强了这一功能,利用Monad强化了程序对Null的处理。