澳门新浦京娱乐游戏Java函数式开发——优雅的Optional空指针处理

鬼魅一般的null阻碍我们进步

如果你是一位聚焦于传统面向对象开发的Javaer,或许你已经习惯了null带来的种种问题。但是早在许多年前,大神就说了null这玩意就是个坑。

托尼.霍尔(你不知道这货是谁吗?自己去查查吧)曾经说过:“I call it my
billion-dollar mistake. It was the invention of the null reference in

  1. I couldn’t resist the temptation to put in a null reference,
    simply because it was so easy to
    implement.”(大意是:“哥将发明null这事称为价值连城的错误。因为在1965那个计算机的蛮荒时代,空引用太容易实现,让哥根本经不住诱惑发明了空指针这玩意。”)。

然后,我们再看看null还会引入什么问题。

看看下面这个代码:

String address = person.getCountry().getProvince().getCity();

如果你玩过一些函数式语言(Haskell、Erlang、Clojure、Scala等等),上面这样是一种很自然的写法。用Java当然也可以实现上面这样的编写方式。

但是为了完满的处理所有可能出现的null异常,我们不得不把这种优雅的函数编程范式改为这样:

if (person != null) {
    Country country = person.getCountry();
    if (country != null) {
        Province province = country.getProvince();
        if (province != null) {
            address = province.getCity();
        }
    }
}

瞬间,高逼格的函数式编程Java8又回到了10年前。这样一层一层的嵌套判断,增加代码量和不优雅还是小事。更可能出现的情况是:在大部分时间里,人们会忘记去判断这可能会出现的null,即使是写了多年代码的老人家也不例外。

上面这一段层层嵌套的 null
处理,也是传统Java长期被诟病的地方。如果以Java早期版本作为你的启蒙语言,这种get->if
null->return
的臭毛病会影响你很长的时间(记得在某国外社区,这被称为:面向entity开发)。

orElseGet

orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。示例如下:

//orElseGet与orElse方法类似,区别在于orElse传入的是默认值,
//orElseGet可以接受一个lambda表达式生成默认值。
//输出:Default Value
System.out.println(empty.orElseGet(() ->
  "Default Value"
));

//输出:Sanaulla

System.out.println(name.orElseGet(() ->
"Default Value"
));

摘要

空闲时会抽空学习同在jvm上运行的Groovy和Scala,发现他们对null的处理比早期版本Java慎重很多。在Java8中,Optional为函数式编程的null处理给出了非常优雅的解决方案。本文将说明长久以来Java中对null的蹩脚处理,然后介绍使用Optional来实现Java函数式编程。

orElseThrow

如果有值则将其返回,否则抛出supplier接口创建的异常。

在orElseGet方法中,我们传入一个Supplier接口。然而,在orElseThrow中我们可以传入一个lambda表达式或方法,如果值不存在来抛出异常。示例如下:

try{
//orElseThrow与orElse方法类似。与返回默认值不同,
//orElseThrow会抛出lambda表达式或方法生成的异常
empty.orElseThrow(ValueAbsentException::new);
}catch(Throwable ex) {
//输出: No value present in the Optional instance
System.out.println(ex.getMessage());
}

ValueAbsentException定义如下:

class ValueAbsentException extends Throwable {

  public ValueAbsentException() {
    super();
  }

  public ValueAbsentException(String msg) {
    super(msg);
  }

  @Override
  public String getMessage() {
    return "No value present in the Optional instance";
  }
}

那些年困扰着我们的null

在Java江湖流传着这样一个传说:直到真正了解了空指针异常,才能算一名合格的Java开发人员。在我们逼格闪闪的java码字符生涯中,每天都会遇到各种null的处理,像下面这样的代码可能我们每天都在反复编写:

if(null != obj1){
  if(null != obje2){
     // do something
  }
}

稍微有点眼界javaer就去干一些稍有逼格的事,弄一个判断null的方法:

boolean checkNotNull(Object obj){
  return null == obj ? false : true; 
}

void do(){
  if(checkNotNull(obj1)){
     if(checkNotNull(obj2)){
        //do something
     }
  }
}

然后,问题又来了:如果一个null表示一个空字符串,那””表示什么?

然后惯性思维告诉我们,””和null不都是空字符串码?索性就把判断空值升级了一下:

boolean checkNotBlank(Object obj){
  return null != obj && !"".equals(obj) ? true : false; 
}
void do(){
  if(checkNotBlank(obj1)){
     if(checkNotNull(obj2)){
        //do something
     }
  }
}

有空的话各位可以看看目前项目中或者自己过往的代码,到底写了多少和上面类似的代码。

不知道你是否认真思考过一个问题:一个null到底意味着什么?

  1. 浅显的认识——null当然表示“值不存在”。
  2. 对内存管理有点经验的理解——null表示内存没有被分配,指针指向了一个空地址。
  3. 稍微透彻点的认识——null可能表示某个地方处理有问题了,也可能表示某个值不存在。
  4. 被虐千万次的认识——哎哟,又一个NullPointerException异常,看来我得加一个if(null
    != value)
    了。

回忆一下,在咱们前面码字生涯中到底遇到过多少次java.lang.NullPointerException异常?NullPointerException作为一个RuntimeException级别的异常不用显示捕获,若不小心处理我们经常会在生产日志中看到各种由NullPointerException引起的异常堆栈输出。而且根据这个异常堆栈信息我们根本无法定位到导致问题的原因,因为并不是抛出NullPointerException的地方引发了这个问题。我们得更深处去查询什么地方产生了这个null,而这个时候日志往往无法跟踪。

有时更悲剧的是,产生null值的地方往往不在我们自己的项目代码中。这就存在一个更尴尬的事实——在我们调用各种良莠不齐第三方接口时,说不清某个接口在某种机缘巧合的情况下就会返回一个null……

回到前面对null的认知问题。很多javaer认为null就是表示“什么都没有”或者“值不存在”。按照这个惯性思维我们的代码逻辑就是:你调用我的接口,按照你给我的参数返回对应的“值”,如果这条件没法找到对应的“值”,那我当然返回一个null给你表示没有“任何东西”了。我们看看下面这个代码,用很传统很标准的Java编码风格编写:

class MyEntity{
   int id;
   String name;
   String getName(){
      return name;
   }
}

// main
public class Test{
   public static void main(String[] args) 
       final MyEntity myEntity = getMyEntity(false);
       System.out.println(myEntity.getName());
   }

   private getMyEntity(boolean isSuc){
       if(isSuc){
           return new MyEntity();
       }else{
           return null;
       }
   }
}

这一段代码很简单,日常的业务代码肯定比这个复杂的多,但是实际上我们大量的Java编码都是按这种套路编写的,懂货的人一眼就可以看出最终肯定会抛出NullPointerException。但是在我们编写业务代码时,很少会想到要处理这个可能会出现的null(也许API文档已经写得很清楚在某些情况下会返回null,但是你确保你会认真看完API文档后才开始写代码么?),直到我们到了某个测试阶段,突然蹦出一个NullPointerException异常,我们才意识到原来我们得像下面这样加一个判断来搞定这个可能会返回的null值。

// main
public class Test{
   public static void main(String[] args) 
       final MyEntity myEntity = getMyEntity(false);
       if(null != myEntity){
           System.out.println(myEntity.getName());
       }else{
           System.out.println("ERROR");
       }
   }
}

仔细想想过去这么些年,咱们是不是都这样干过来的?如果直到测试阶段才能发现某些null导致的问题,那么现在问题就来了——在那些雍容繁杂、层次分明的业务代码中到底还有多少null没有被正确处理呢?

对于null的处理态度,往往可以看出一个项目的成熟和严谨程度。比如Guava早在JDK1.6之前就给出了优雅的null处理方式,可见功底之深。

map

map方法文档说明如下:
如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。

map方法用来对Optional实例的值执行一系列操作。通过一组实现了Function接口的lambda表达式传入操作。如果你不熟悉Function接口,可以参考我的这篇博客。map方法示例如下:

//map方法执行传入的lambda表达式参数对Optional实例的值进行修改。
//为lambda表达式的返回值创建新的Optional实例作为map方法的返回值。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));

flatMap
如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。

flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional。
参照map函数,使用flatMap重写的示例如下:

//flatMap与map(Function)非常类似,区别在于传入方法的lambda表达式的返回类型。
//map方法中的lambda表达式返回值可以是任意类型,在map函数返回之前会包装为Optional。 
//但flatMap方法中的lambda表达式返回值必须是Optionl实例。 
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//输出SANAULLA

filter
filter个方法通过传入限定条件对Optional实例的值进行过滤。文档描述如下:
如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。

读到这里,可能你已经知道如何为filter方法传入一段代码。是的,这里可以传入一个lambda表达式。对于filter函数我们应该传入实现了Predicate接口的lambda表达式。如果你不熟悉Predicate接口,可以参考这篇文章。
现在我来看看filter的各种用法,下面的示例介绍了满足限定条件和不满足两种情况:

//filter方法检查给定的Option值是否满足某些条件。
//如果满足则返回同一个Option实例,否则返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//输出Sanaulla

//另一个例子是Optional值不满足filter指定的条件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
//输出:name长度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));

以上,我们介绍了Optional类的各个方法。下面通过一个完整的示例对用法集中展示:

public class OptionalDemo {

  public static void main(String[] args) {
    //创建Optional实例,也可以通过方法返回值得到。
    Optional<String> name = Optional.of("Sanaulla");

    //创建没有值的Optional实例,例如值为'null'
    Optional empty = Optional.ofNullable(null);

    //isPresent方法用来检查Optional实例是否有值。
    if (name.isPresent()) {
      //调用get()返回Optional值。
      System.out.println(name.get());
    }

    try {
      //在Optional实例上调用get()抛出NoSuchElementException。
      System.out.println(empty.get());
    } catch (NoSuchElementException ex) {
      System.out.println(ex.getMessage());
    }

    //ifPresent方法接受lambda表达式参数。
    //如果Optional值不为空,lambda表达式会处理并在其上执行操作。
    name.ifPresent((value) -> {
      System.out.println("The length of the value is: " + value.length());
    });

    //如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
    System.out.println(empty.orElse("There is no value present!"));
    System.out.println(name.orElse("There is some value!"));

    //orElseGet与orElse类似,区别在于传入的默认值。
    //orElseGet接受lambda表达式生成默认值。
    System.out.println(empty.orElseGet(() -> "Default Value"));
    System.out.println(name.orElseGet(() -> "Default Value"));

    try {
      //orElseThrow与orElse方法类似,区别在于返回值。
      //orElseThrow抛出由传入的lambda表达式/方法生成异常。
      empty.orElseThrow(ValueAbsentException::new);
    } catch (Throwable ex) {
      System.out.println(ex.getMessage());
    }

    //map方法通过传入的lambda表达式修改Optonal实例默认值。 
    //lambda表达式返回值会包装为Optional实例。
    Optional<String> upperName = name.map((value) -> value.toUpperCase());
    System.out.println(upperName.orElse("No value found"));

    //flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
    //map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
    //但是flatMap方法的lambda返回值总是Optional类型。
    upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
    System.out.println(upperName.orElse("No value found"));

    //filter方法检查Optiona值是否满足给定条件。
    //如果满足返回Optional实例值,否则返回空Optional。
    Optional<String> longName = name.filter((value) -> value.length() > 6);
    System.out.println(longName.orElse("The name is less than 6 characters"));

    //另一个示例,Optional值不满足给定条件。
    Optional<String> anotherName = Optional.of("Sana");
    Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
    System.out.println(shortName.orElse("The name is less than 6 characters"));

  }

}

上述代码输出如下:

Sanaulla
No value present
The length of the value is: 8
There is no value present!
Sanaulla
Default Value
Sanaulla
No value present in the Optional instance
SANAULLA
SANAULLA
Sanaulla
The name is less than 6 characters

澳门新浦京娱乐游戏 ,原文链接:
javacodegeeks
翻译:
ImportNew.com-
高俊阳译文链接:
http://www.importnew.com/6675.html

写在最后的

Optional只是Java函数式编程的冰山一角,需要结合lambdastreamFuncationinterface等特性才能真正的了解Java8函数式编程的效用。本来还想介绍一些Optional的源码和运行原理的,但是Optional本身的代码就很少、API接口也不多,仔细想想也没什么好说的就省略了。

Optional虽然优雅,但是个人感觉有一些效率问题,不过还没去验证。如果有谁有确实的数据,请告诉我。

本人也不是“函数式编程支持者”。从团队管理者的角度来说,每提升一点学习难度,人员的使用成本和团队交互成本就会更高一些。就像在传说中Lisp可以比C++的代码量少三十倍、开发更高效,但是若一个国内的常规IT公司真用Lisp来做项目,请问去哪、得花多少钱弄到这些用Lisp的哥们啊?

但是我非常鼓励大家都学习和了解函数式编程的思路。尤其是过去只侵淫在Java这一门语言、到现在还不清楚Java8会带来什么改变的开发人员,Java8是一个良好的契机。更鼓励把新的Java8特性引入到目前的项目中,一个长期配合的团队以及一门古老的编程语言都需要不断的注入新活力,否则不进则退。

of

为非null的值创建一个Optional。

of方法通过工厂方法创建Optional类。需要注意的是,创建对象时传入的参数不能为null。如果传入参数为null,则抛出NullPointerException

//调用工厂方法创建Optional实例
Optional<String> name = Optional.of("Sanaulla");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);

利用Optional实现Java函数式编程

好了,说了各种各样的毛病,然后我们可以进入新时代了。

早在推出Java SE
8版本之前,其他类似的函数式开发语言早就有自己的各种解决方案。下面是Groovy的代码:

String version = computer?.getSoundcard()?.getUSB()?.getVersion():"unkonwn";

Haskell用一个 Maybe
类型类标识处理null值。而号称多范式开发语言的Scala则提供了一个和Maybe差不多意思的Option[T],用来包裹处理null。

Java8引入了 java.util.Optional<T>来处理函数式编程的null问题,Optional<T>的处理思路和Haskell、Scala类似,但又有些许区别。先看看下面这个Java代码的例子:

public class Test {
    public static void main(String[] args) {
        final String text = "Hallo world!";
        Optional.ofNullable(text)//显示创建一个Optional壳
            .map(Test::print)
            .map(Test::print)
            .ifPresent(System.out::println);

        Optional.ofNullable(text)
            .map(s ->{ 
                System.out.println(s);
                return s.substring(6);
            })
            .map(s -> null)//返回 null
            .ifPresent(System.out::println);
    }
    // 打印并截取str[5]之后的字符串
    private static String print(String str) {
        System.out.println(str);
        return str.substring(6);
    }
}
//Consol 输出
//num1:Hallo world!
//num2:world!
//num3:
//num4:Hallo world!

* (可以把上面的代码copy到你的IDE中运行,前提是必须安装了JDK8。)*

上面的代码中创建了2个Optional,实现的功能基本相同,都是使用Optional作为String的外壳对String进行截断处理。当在处理过程中遇到null值时,就不再继续处理。我们可以发现第二个Optional中出现s->null之后,后续的ifPresent不再执行。

注意观察输出的 //num3:,这表示输出了一个””字符,而不是一个null。

Optional提供了丰富的接口来处理各种情况,比如可以将代码修改为:

public class Test {
    public static void main(String[] args) {
        final String text = "Hallo World!";
        System.out.println(lowerCase(text));//方法一
        lowerCase(null, System.out::println);//方法二
    }

    private static String lowerCase(String str) {
        return Optional.ofNullable(str).map(s -> s.toLowerCase()).map(s->s.replace("world", "java")).orElse("NaN");
    }

    private static void lowerCase(String str, Consumer<String> consumer) {
        consumer.accept(lowerCase(str));
    }
}
//输出
//hallo java!
//NaN

这样,我们可以动态的处理一个字符串,如果在任何时候发现值为null,则使用orElse返回预设默认的“NaN”

总的来说,我们可以将任何数据结构用Optional包裹起来,然后使用函数式的方式对他进行处理,而不必关心随时可能会出现的null

我们看看前面提到的Person.getCountry().getProvince().getCity()怎么不用一堆if来处理。

第一种方法是不改变以前的entity:

import java.util.Optional;
public class Test {
    public static void main(String[] args) {
        System.out.println(Optional.ofNullable(new Person())
            .map(x->x.country)
            .map(x->x.provinec)
            .map(x->x.city)
            .map(x->x.name)
            .orElse("unkonwn"));
    }
}
class Person {
    Country country;
}
class Country {
    Province provinec;
}
class Province {
    City city;
}
class City {
    String name;
}

这里用Optional作为每一次返回的外壳,如果有某个位置返回了null,则会直接得到”unkonwn”。

第二种办法是将所有的值都用Optional来定义:

import java.util.Optional;
public class Test {
    public static void main(String[] args) {
        System.out.println(new Person()
                .country.flatMap(x -> x.provinec)
                .flatMap(Province::getCity)
                .flatMap(x -> x.name)
                .orElse("unkonwn"));
    }
}
class Person {
    Optional<Country> country = Optional.empty();
}
class Country {
    Optional<Province> provinec;
}
class Province {
    Optional<City> city;
    Optional<City> getCity(){//用于::
        return city;
    }
}
class City {
    Optional<String> name;
}

第一种方法可以平滑的和已有的JavaBean、EntityPOJA整合,而无需改动什么,也能更轻松的整合到第三方接口中(例如springbean)。建议目前还是以第一种Optional的使用方法为主,毕竟不是团队中每一个人都能理解每个get/set带着一个Optional的用意。

Optional还提供了一个filter方法用于过滤数据(实际上Java8stream风格的接口都提供了filter方法)。例如过去我们判断值存在并作出相应的处理:

if(Province!= null){
  City city = Province.getCity();
  if(null != city && "guangzhou".equals(city.getName()){
    System.out.println(city.getName());
  }else{
    System.out.println("unkonwn");
  }
}

现在我们可以修改为

Optional.ofNullable(province)
   .map(x->x.city)
   .filter(x->"guangzhou".equals(x.getName()))
   .map(x->x.name)
   .orElse("unkonw");

到此,利用Optional来进行函数式编程介绍完毕。Optional除了上面提到的方法,还有orElseGetorElseThrow等根据更多需要提供的方法。orElseGet会因为出现null值抛出空指针异常,而orElseThrow会在出现null时,抛出一个使用者自定义的异常。可以查看API文档来了解所有方法的细节。

get

如果Optional有值则将其返回,否则抛出NoSuchElementException

上面的示例中,get方法用来得到Optional实例中的值。下面我们看一个抛出NoSuchElementException的例子:

//执行下面的代码会输出:No value present
try{ 
   //在空的Optional实例上调用get(),抛出NoSuchElementException
    System.out.println(empty.get());
}catch (NoSuchElementException ex) {
   System.out.println(ex.getMessage());
}

ifPresent

如果Optional实例有值则为其调用consumer,否则不做处理

要理解ifPresent方法,首先需要了解Consumer类。简答地说,Consumer类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。Java8支持不用接口直接通过lambda表达式传入参数。
如果Optional实例有值,调用ifPresent()可以接受接口段或lambda表达式。类似下面的代码:

//ifPresent方法接受lambda表达式作为参数。
//lambda表达式对Optional的值调用consumer进行处理。
name.ifPresent((value) -> {
System.out.println(
"The length of the value is: "
+ value.length());
});

转载自
http://www.importnew.com/6675.html

ofNullable

为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。

ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况。示例如下:

//下面创建了一个不包含任何值的Optional实例
//例如,值为'null'
Optional empty = Optional.ofNullable(null);

身为一名Java程序员,大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法。我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的参数。这正是一些类似Guava的外部API试图解决的问题。一些JVM编程语言比如Scala、Ceylon等已经将对在核心API中解决了这个问题。在我的前一篇文章中,介绍了Scala是如何解决了这个问题。
新版本的Java,比如Java
8引入了一个新的Optional类。Optional类的Javadoc描述如下:
这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

本文会逐个探讨Optional类包含的方法,并通过一两个示例展示如何使用。

orElse

如果有值则将其返回,否则返回指定的其它值。

如果Optional实例有值则将其返回,否则返回orElse方法传入的参数。示例如下:

//如果值不为null,orElse方法返回Optional实例的值。
//如果为null,返回传入的消息。
//输出:There is no value present!
System.out.println(empty.orElse(
  "There is no value present!"
));
//输出:Sanaulla
System.out.println(name.orElse(
  "There is some value!"
));

isPresent

非常容易理解
如果值存在返回true,否则返回false。

类似下面的代码:

//isPresent方法用来检查Optional实例中是否包含值
if(name.isPresent()) {
//在Optional实例内调用get()返回已存在的值
System.out.println(name.get());
//输出Sanaulla
}
You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图