Java企业教程系列
使用Java8的Lambda实现Monad
Monad是函数语言(Cojure或Scala)中的设计模式概念, 那么现在为什么在Java中变得如此重要?因为Java从版本8以后引入了新的Lambda特性,Lambda或闭包是函数语言的特征,它允许你使用代码块作为一个变量,并且让你将它传递。
首先我们假设一个不使用Monad的场景:空值Null检查:
public static class Userdetails{
public Address address;
public Name name;
}
public static class Name{
public String firstName;
public String lastName;
}
public static class Address{
public String houseNumber;
public Street street;
public City city;
}
public static class Street{
public String name;
}
public static class City{
public String name;
}
现在你要从UserDetails访问其street接到名称,有可能是空的,不用Monad你也许是如下进行检查:
if(user == null )
return null;
else if(user.address == null)
return null;
else if(user.address.street == null)
return null;
else
return user.address.street.name;
这是非常琐碎和噪音的。
让我们创建一个Option类代表一个可选值,然后有一个围绕其运行lambda的map方法,该方法返回另外一个可选值。如果围绕的值是一个null, 它会返回一个Option,其中包含null, 这样避免空指针出现,注意map方法需要使用一个lambda作为参数,但是我们需要首先创建一个接口SingleArgExpression 支持它。因为在Java8中使用函数接口表达lambda:
public interface SingleArgExpression<P, R> {
public R function(P param);
}
准备好了函数接口,这样我们可以将SingleArgExpression的lambda作为参数传递了,下面是Option.java,关键是其中的map方法,该方法参数是SingleArgExpression的lambda。
public class Option<T> {
T value;
public Option(T value){
this.value = value;
}
//核心重要方法map 如果是空就不应用lambda,否则就可以使用lambda
public <E> Option<E> map(SingleArgExpression<T,E> mapper){
if(value == null){
return new Option<E>(null);
}else{
return new Option<E>(mapper.function(value));
}
}
@Override
public boolean equals(Object rhs){
if(rhs instanceof Option){
Option o = (Option)rhs;
if(value == null)
return (o.value==null);
else{
return value.equals(o.value);
}
}else{
return false;
}
}
@Override
public int hashCode(){
return value==null? 0 : value.hashCode();
}
public T get(){
return value;
}
}
下面我们重新编写Userdetails如下:
public class OptionExample{
public static class Userdetails{
public Option<Address> address = new Option<>(null);
public Option<Name> name = new Option<>(null);
}
public static class Name{
public Option<String> firstName = new Option<String>(null);
public Option<String> lastName = new Option<String>(null);
}
public static class Address{
public Option<String> houseNumber;
public Option<Street> street;
public Option<City> city;
}
public static class Street{
public Option<String> name;
}
public static class City{
public Option<String> name;
}
public static void main(String [] args){
Option<Userdetails> userOpt = new Option<>(new Userdetails());
//现在变得很简单了。
String streetName = userOpt.flatMap(user -> user.address).map(address -> address.street).map(street -> street.name).get();
System.out.println(streetName);
}
}
核心代码如下:
String streetName = userOpt.flatMap(user -> user.address).map(address -> address.street).map(street -> street.name).get();
这样就不用担心返回空值抛出空指针错误了。
现在UserDetails的所有方法都是返回一个Option了,这确保方法的使用者了解返回值可能会为空。
为了避免核心代码中每次都调用get(), 我们使用类似flatMap替代map:
public <E> Option<E> flatMap(SingleArgExpression<T, Option<E>> mapper){
if(value == null){
return new Option<E>(null);
}
return mapper.function(value);
}
上面方法其实是一个过滤器,它让我们放入一个if条件在map链中, 这样只有当条件为真是获得一个值,注意这也是空安全null-safe。