欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java8 Optional

程序员文章站 2022-03-04 10:52:26
...

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

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

  1. Java Docs – Optional