coding……
但行好事 莫问前程

java8 Optional

Optional是Guava提出的概念,通过使用检查空值的方式来防止代码污染,鼓励程序员写更干净的代码,解决空指针异常NullPointerException。受到Google Guava的启发,Optional在Java8正式加入Java豪华套餐。Optional实际上是个容器,它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。由于Java8的Optional用起来比Guava的Optional更方便,所以在本篇学习笔记中,我介绍一下Java8的Optional的使用。

Optional容器构造方法:

S.N. 方法及说明
1 static<T> Optional<T> empty()
null包装成的Optional对象
2 static <T> Optional<T> of(T value)
构造一个Optional对象,参数不能是null
3 static <T> Optional<T> ofNullable(T value)
构造一个Optional对象,参数允许为null

Optional容器常用方法:

S.N. 方法及说明
1 boolean isPresent()
检查持有的value是否为null
2 void ifPresent(Consumer<? super T> consumer)
如果持有的value不为null,通过Consumer函数式接口,对持有的value做相应的操作,比如打印等void操作
3 T orElse(T other)
如果持有的Optional对象isPresent,则返回持有的value,否则返回orElse方法的参数,可用来设置默认值
4 T orElseGet(Supplier<? extends T> other)
如果持有的Optional对象isPresent,则返回持有的value,否则通过Supplier函数式接口取回一个对象作为默认值返回
5 <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
如果持有的Optional对象isPresent,则返回持有的value,否则抛出一个异常
6 T get()
取回持有的value,如果value为null,则抛出异常NoSuchElementException
7 Optional<T> filter(Predicate<? super T> predicate)
通过Predicate函数式接口定义的规则测试value,如果函数式接口返回true,则返回原Optional对象,否则通过 empty()方法返回一个Optional对象,可用来对Optional进行过滤
8 Optional<U> map(Function<? super T, ? extends U> mapper)
对value通过Function函数式接口定义的规则返回一个对象,然后将返回的对象包装成一个Optional对象返回
9 Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
对value通过Function函数式接口定义的规则返回一个Optional对象,然后将返回的Optional对象返回
  • isPresent

当从其他方法获取一个Optional对象或者自己创建一个Optional对象,可以使用isPresent判断Optional持有的value对象是否存在

@Test
public void testIsPresent() {
    Optional<String> opt = Optional.of("zhuoli");
    assertTrue(opt.isPresent());

    opt = Optional.ofNullable(null);
    assertFalse(opt.isPresent());
}
  • ifPresent

当Optional对象持有的value不为null时,可以使用ifPresent接口针对value做一些操作。比如没有使用Optional时,我们有可能回做如下操作:

if(name != null){
    System.out.println(name.length);
}

当使用Optional后,我们可以如下操作:

@Test
public void testIfPresent() {
    Optional<String> opt = Optional.of("zhuoli");
    opt.ifPresent(name -> System.out.println(name.length()));
}
  • orElse

orElse可以用来获取Optional持有的value值,方法的参数作为默认值。如果Optional持有的value值不为null,返回value值,否则返回默认值。

@Test
public void orElseTest() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElse("zhuoli");
    assertEquals("zhuoli", name);
}
  • orElseGet

orElseGet功能与orElse相似,也是用来获取Optional持有的value值。不同的是当Optional持有的value值不为null时,它不是直接返回一个值,而是通过函数式接口的调用返回一个值。

@Test
public void orElseGetTest() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseGet(() -> "zhuoli");
    assertEquals("zhuoli", name);
}
  • orElse与orElseGet的异同点

orElseGet功能与orElse相似,但是,这两者之间也存在一个非常重要的差异。如果不能很好地理解,会大幅度地影响代码的性能。通过如下的代码,大家可以看到两者之间的不同,首先定义一个取默认值得方法:

public String getMyDefault() {
    System.out.println("Getting Default Value");
    return "Default Value";
}

通过如下两个测试,看一下它们之间的差异:

@Test
public void orElseAndOrElseTestWhenOptionalValueIsNull() {
    String text = null;

    System.out.println("Using orElseGet:");
    String defaultText =
            Optional.ofNullable(text).orElseGet(this::getMyDefault);
    assertEquals("Default Value", defaultText);

    System.out.println("Using orElse:");
    defaultText = Optional.ofNullable(text).orElse(getMyDefault());
    assertEquals("Default Value", defaultText);
}

运行结果:

Connected to the target VM, address: '127.0.0.1:14005', transport: 'socket'
Using orElseGet:
Getting Default Value
Using orElse:
Getting Default Value
Disconnected from the target VM, address: '127.0.0.1:14005', transport: 'socket'

Process finished with exit code 0
@Test
public void orElseAndOrElseTestWhenOptionalValueIsNotNull() {
    String text = "zhuoli";

    System.out.println("Using orElseGet:");
    String defaultText =
            Optional.ofNullable(text).orElseGet(this::getMyDefault);
    assertEquals("zhuoli", defaultText);

    System.out.println("Using orElse:");
    defaultText = Optional.ofNullable(text).orElse(getMyDefault());
    assertEquals("zhuoli", defaultText);
}

运行结果:

Connected to the target VM, address: '127.0.0.1:14022', transport: 'socket'
Using orElseGet:
Using orElse:
Getting Default Value
Disconnected from the target VM, address: '127.0.0.1:14022', transport: 'socket'

Process finished with exit code 0

通过上述两个例子可以看出,当Optional持有的value为null时,都会去调用getMyDefault()方法,去获取默认值,这一点没什么好讲的。但当Optional持有的value不为null时,orEleseGet不会去调用getMyDefault()方法,直接返回了Optional持有的value。orElse虽然也返回了Optional持有的value,但是同时也去调用了getMyDefault()方法。上述简单的方法在性能上不会有什么区别,但是当getMyDefault()是一个非常复杂的方法,或者外部调用时,多调用一次无谓的方法,确实也是一种负担。所以当使用方法调用取默认值时,建议统一使用orElseGet。

  • orElseThrow

orElseThrow也可以用来取默认值,不同的是,当Optional持有的value为null时,直接抛出异常。

@Test(expected = IllegalArgumentException.class)
public void orElseThrowTest() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseThrow(
            IllegalArgumentException::new);
}
  • get

取回Optional持有的value,如果value为null,则抛出异常NoSuchElementException。

@Test
public void getTest() {
    Optional<String> opt = Optional.of("zhuoli");
    String name = opt.get();

    assertEquals("zhuoli", name);
}

@Test(expected = NoSuchElementException.class)
public void getTestWhenValueIsNull() {
Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();
}
  • filter

通过Predicate函数式接口定义的规则测试value,如果函数式接口返回true,则返回原Optional对象,否则通过 empty()方法返回一个Optional对象,可用来对Optional进行过滤,如下:

@Test
public void filterTest() {
    Integer year = 2016;
    Optional<Integer> yearOptional = Optional.of(year);
    boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent();
    assertTrue(is2016);
    boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent();
    assertFalse(is2017);
}

filter常用于对Optional对象持有的value做一些判断,返回一个boolean值,会比原来直接使用POJO方便很多,如下定义一个POJO:

public class Modem {
    private Double price;
 
    public Modem(Double price) {
        this.price = price;
    }
    //standard getters and setters
}

在不适用Optional对象时,我们判断一个Modem对象的price是否在某个范围之内,我们做如下很麻烦的操作:

public static boolean priceIsInRange1(Modem modem) {
    boolean isInRange = false;

    if (modem != null && modem.getPrice() != null
            && (modem.getPrice() >= 10
            && modem.getPrice() <= 15)) {

        isInRange = true;
    }
    return isInRange;
}

但在使用Optional后,可以做如下简化:

public static boolean priceIsInRange2(Modem modem2) {
    return Optional.ofNullable(modem2)
            .map(Modem::getPrice)
            .filter(p -> p >= 10)
            .filter(p -> p <= 15)
            .isPresent();
}

Optional带来的好处就是不用在显式的层层去检查一个对象是否为null了,并且支持链式操作,代码看起来要比原来“优雅”很多。

  • map

map可以用来转化Optional对象,对value通过Function函数式接口定义的规则返回一个对象,然后将返回的对象包装成一个Optional对象返回。

@Test
public void mapTest() {
    List<String> companyNames = Lists.newArrayList(
            "paypal", "oracle", "", "microsoft", "", "apple");
    Optional<List<String>> listOptional = Optional.of(companyNames);

    int size = listOptional
            .map(List::size)
            .orElse(0);
    assertEquals(6, size);
}

假设我们想要检查用户输入的密码的正确性,我们可以通过map去除密码中非法的空格字符,然后通过filter过滤密码是否正确:

@Test
public void filterMapTest1() {
    String password = " 123456 ";
    Optional<String> passOpt = Optional.of(password);
    boolean correctPassword = passOpt.filter(
      pass -> pass.equals("password")).isPresent();
    assertFalse(correctPassword);
 
    correctPassword = passOpt
      .map(String::trim)
      .filter(pass -> pass.equals("password"))
      .isPresent();
    assertTrue(correctPassword);
}
  • flatMap

flatMap也可以用来转化Optional对象,我也一直很迷惑两者之间的区别,通过看源码,发现它与Map的区别在于,对value通过Function函数式接口定义的规则的返回是不同的,Map直接返回一个对象,但是flatMap返回的是个Optional对象。源码如下

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        /*将函数式接口返回的对象调用ofNullable包装为optional对象返回*/
        return Optional.ofNullable(mapper.apply(value));
    }
}

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        /*直接将函数式接口返回的Optional对象返回*/
        return Objects.requireNonNull(mapper.apply(value));
    }
}

通过如下代码相信大家都可以看出区别所在,首先定义一个Person类:

public class Person {
    private String name;
    private int age;
    private String password;
 
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
 
    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }
 
    public Optional<String> getPassword() {
        return Optional.ofNullable(password);
    }
 
    // normal constructors and setters
}
@Test
public void filterMapTest1() {
    Person person = new Person("zhuoli",18,"123");
    Optional<Person> personOptional = Optional.of(person);

    Optional<Optional<String>> nameOptionalWrapper
            /*Person::getName返回Optional<String>,map方法嵌套Optional<String>对象返回*/
            = personOptional.map(Person::getName);
    Optional<String> nameOptional
            = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
    String name1 = nameOptional.orElse("");
    assertEquals("zhuoli", name1);

    String name = personOptional
            /*Person::getName返回Optional<String>,flatMap直接将Optional<String>对象返回*/
            .flatMap(Person::getName)
            .orElse("");
    assertEquals("zhuoli", name);
}

当Optional对象持有的value对象包含Optional成员变量或者get方法返回Optional对象时,使用flatMap操作,可以直接获取不嵌套的Optional对象。

最后,给大家分享一篇Optional相关的博客使用 Java8 Optional 的正确姿势,感觉写的很不错。其中的观点绝大多数我都是认同的,除了博主讲使用isPresent()方法,说明你对Optional的使用姿势是不对的。比如我在本文中提到的,通过和filter配合,最后通过isPresent判断对象是不是满足条件就是isPresent很不错的一个应用。

测试代码:码云 – 卓立 – Java8 Optional测试代码

  1. Java Docs – Optional

赞(2) 打赏
Zhuoli's Blog » java8 Optional
分享到: 更多 (0)

评论 2

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    楼主,我是 【Java】jdk8 Optional 的正确姿势 原作者,原始链接在我的博客 https://unmi.cc/proper-ways-of-using-java8-optional/。写的文章经几番转载,原链接就不说了,连标题都走样了。
    感谢对 isPresent 方法使用上的点评,当时对 filter 确实未作深入。

    Yanbin6年前 (2018-07-24)回复
    • 感谢评论,楼主那篇文章写的确实很好,希望可以共同进步,文章中的链接已修改,这个几经转载确实有点无奈==

      zhuoli6年前 (2018-07-25)回复