Optional方法解释以及使用

Optional

1、介绍

先来看看 Optional 设计出来的意图是什么, Java 语言架构师 Brian Goetz 是这么说的:

Optional 可以给返回结果提供了一个表示无结果的值,而不是返回 null,其实是一个空的Optional的空实例对象。

Optional 其实就是一个箱子,里面存储着物品。那么这个箱子面可以存放的有东西,可能存放的也没有东西。

Optional可以很方便的帮助我们来解决NPE的问题,但是并非是说一定不会发生NPE问题,当不小心使用的时候也还是会发生的。Optional类通过一些操作封装,表现出来好像用 Optional 就不需要关心空指针的情况。

而事实上是 Optional 在替我们负重前行,该有的判断它替我们完成了,而且用了 Optional 最后拿结果的时候还是小心的,盲目 get 一样会抛错,Brian Goetz 说 get 应该叫 getOrElseThrowNoSuchElementException。

来一个案例来进行演示:

Yes yes = getYes();
if (yes != null) {
    Address yesAddress = yes.getAddress();
    if (yesAddress != null) {
        Province province = yesAddress.getProvince();
        System.out.println(province.getName());
    }
}
throw new NoSuchElementException(); //如果没找到就抛错

对上面的代码进行分析的话,如果yes为null,那么就直接抛出没有元素异常;如果存在,那么获取得到地址;如果地址也存在,那么获取得到市等等操作。将上面这段话翻译成Optional的使用就是下面这种方式

如果用 Optional 的话,那就变成下面这样:

Optional.ofNullable(getYes())
        .map(a -> a.getAddress())
        .map(p -> p.getProvince())
        .map(n -> n.getName())
        .orElseThrow(NoSuchElementException::new);

可以看到,如果用了 Optional,代码里不需要判空的操作,即使 address 、province 为空的话,也不会产生空指针错误,这就是 Optional 带来的好处!

说到这,我想提个问:

如果在 a.getAddress() 时拿不到值的话,你说是会继续执行map(p -> p.getProvince()) 还是直接跳到 orElseThrow? 或者反过来如果 map(n -> n.getName()) 不为空,你说 orElseThrow 这个方法会不会执行?

答案是否定的。即使ofNullable方法中的值是null,也还是会执行map方法

看下源码来解释一下上面代码执行的流程:

private static final Optional<?> EMPTY = new Optional<>();

private final T value;

// 私有空参构造,对value赋值为null
private Optional() {
    this.value = null;
}

// 私有构造,value为null,抛出异常;不为null,那么返回不是EMPTY的对象实例
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

public static<T> Optional<T> empty() {
	// 返回一个Optional类的EMPTY对象
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

前面提到的:Optional 就是个箱子,里面的 value 才是正主,并且内置了一个 EMPTY 对象,用来替换当 value 为 null 时候的箱子。

也就是说当箱子里面什么都没有的时候,那么箱子里面装的是EMPTY对象;如果有值的时候,装的是实际的对象;

现在看下上面演示的 map 方法,看看它的内部实现是如何让我们不需要做非空判断的。

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        // 有值返回true,没有值返回false。这里没有值就返回EMPTY实例
        if (!isPresent())
            return empty();
        else {
            // 有值的时候,这里会再次来进行判断。对接口中apply方法实现自定义得到想要的值。返回的是Optional类对象实例
            return Optional.ofNullable(mapper.apply(value));
        }
    }

这里的mapper也就是我们是否传入了对应接口实现类的对象,如果value是空的,那么就直接返回一个Optional空实例对象;如果不是空的,那么调用apply中提供的方法,不管value是否是空,可以看到返回的都是Optional类实例,value是由Optional类来进行绑定的。可以看到上面的方法,map都是会执行的。

看完了这些源码,那么再对上面的案例进行解释一波:

如果ofNullable函数返回的是一个EMPTY对象,那么调用了map函数之后,依然返回的是一个EMPTY对象;不是EMPTY对象的时候,那么就一定是有值的,那么就会返回一个转换后的有值的被Optional包裹的对象。那么来进行map的时候,发现不是一个空的,那么久会进行转换,得到另外的值,但是事实上,获取得到返回的是一个value=null的引用而已,然后如果value继续为null,那么将会导致继续返回的是一个EMPTY对象,然后继续操作,直到最终返回的是一个得到指定的值。

再看下下面两个函数of和ofNullable:

// 直接只用Optional.of(value)来传入一个值,通过上面的构造函数可以看到,如果value是空的,那么直接NPE;如果非空,那么返回一个Optional类对象   
public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

ofNullable和上面的使用上非常相似,而且在里面还调用了上面的方法:

    public static <T> Optional<T> ofNullable(T value) {
        // 如果是null,那么就直接返回EMPTY对象;如果不是,那么就返回一个Optional类的非实例对象
        return value == null ? empty() : of(value);
    }

二者的区别是什么?第一个of函数获取得到的如果没有报错的情况下,那么返回的一定是一个非空的Optional对象;第二个如果value是null,那么直接返回一个EMPTY实例对象;否则就返回非空的Optional对象。

也就是说ofNullable也可以返回EMPTY对象,也可以返回非EMPTY对象。第二种的使用方式还是很多的。

2、方法使用详细介绍

Optional类的方法

方法 描述
empty 返回一个空的Optional类实例
of 将对象封装到Optional类中去,要求对象不能够为空,否则返回一个NullPointerException
ofNullable 获取得到Optional类封装的对象,如果对象为空,那么返回一个空实例,如果不为空,返回一个封装了对象的Optional实例
filter 如果值存在而且能够满足提供的谓词,就返回包含该值的Optional对象,否则返回一个空的Optional类实例
map 如果Optional类封装的值存在,那么执行map函数中定义的内容
flatMap 如果该值存在,通过Function函数,返回一个Optional类型的值,否则返回一个空的Optional类实例
get 如果值存在,那么将Optional类实例封装的值返回,否则将会抛出NoSuchElementException异常
isPresent 如果存在封装的对象,那么返回true;如果不存在,那么返回false
ifPresent 如果存在封装的对象,那么执行后面的消费方法;如果不存在,那么不做任何事情
orElse 如果Optional实例对象是empty,那么使用默认的值来代替
orElseGet 如果有值,则获取得到将其返回;如果没有值,那么将会使用指定的函数生成的值
orElseThrow 如果有值将其进行返回,否则抛出一个指定的异常

2.1、创建实例的两种方式

我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

那么在这样子来进行使用之前,需要注意的是可以会出现空指针异常,那么为了避免空指针异常,所以应该来进行判空操作:

if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            if (isocode != null) {
                isocode = isocode.toUpperCase();
            }
        }
    }
}

但是这样子来做,难免会觉得有些难受,大量的判空,只是为了验证一下空指针异常操作。

创建optional类实例

@Test(expected = NoSuchElementException.class)public void whenCreateEmptyOptional_thenNull() {    Optional<User> emptyOpt = Optional.empty();    emptyOpt.get();}

这种方法会来进行尝试访问 emptyOpt 变量的值会导致 NoSuchElementException

你可以使用 of() 和 ofNullable() 方法创建包含值的 Optional。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException

@Test(expected = NullPointerException.class)public void whenCreateOfEmptyOptional_thenNullPointerException() {    Optional<User> opt = Optional.of(user);}

因此,你应该明确对象不为 null 的时候使用 of()。如果对象即可能是 null 也可能是非 null,就应该使用 ofNullable() 方法:

Optional<User> opt = Optional.ofNullable(user);

2.2、访问实例的值

访问optional实例的值

Optional 实例中取回实际值对象的方法之一是使用 get() 方法:

@Testpublic void whenCreateOfNullableOptional_thenOk() {    String name = "John";    Optional<String> opt = Optional.ofNullable(name);    assertEquals("John", opt.get());}

这个方法会在值为 null 的时候抛出异常。要避免异常,你可以选择首先验证是否有值:

但是如果是下面这种情况:

@Testpublic void whenCheckIfPresent_thenOk() {    User user = new User("john@gmail.com", "1234");    Optional<User> opt = Optional.ofNullable(user);    assertTrue(opt.isPresent());     assertEquals(user.getEmail(), opt.get().getEmail());}

检查是否有值的另一个选择是 ifPresent() 方法。该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式:

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

这个例子中,只有 user 用户不为 null 的时候才会执行断言。

2.3、默认值

接下来,我们来看看提供空值的方法。

Optional 类提供了 API 用以返回对象值,或者在对象为空的时候返回默认值。

这里你可以使用的第一个方法是 orElse(),它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值:

@Testpublic void whenEmptyValue_thenReturnDefault() {    User user = null;    User user2 = new User("anna@gmail.com", "1234");    User result = Optional.ofNullable(user).orElse(user2);     assertEquals(user2.getEmail(), result.getEmail());}

这里 user 对象是空的,所以返回了作为默认值的 user2

如果对象的初始值不是 null,那么默认值会被忽略:

@Testpublic void whenValueNotNull_thenIgnoreDefault() {    User user = new User("john@gmail.com","1234");    User user2 = new User("anna@gmail.com", "1234");    User result = Optional.ofNullable(user).orElse(user2);    assertEquals("john@gmail.com", result.getEmail());}

第二个同类型的 API 是 orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果:

User result = Optional.ofNullable(user).orElseGet( () -> user2);

orElse() orElseGet() 的不同之处

乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。我们创建一些示例来突出二者行为上的异同。

我们先来看看对象为空时他们的行为:

@Test
public void givenEmptyValue_whenCompare_thenOk() {
    User user = null
    logger.debug("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.debug("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
 
private User createNewUser() {
    logger.debug("Creating New User");
    return new User("extra@gmail.com", "1234");
}

查看控制台输出:

Using orElse
Creating New User
Using orElseGet
Creating New User

由此可见,当对象为空而返回默认对象时,行为并无差异。

我们接下来看一个类似的示例,但这里 Optional 不为空:

@Test
public void givenPresentValue_whenCompare_thenOk() {
    User user = new User("john@gmail.com", "1234");
    logger.info("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.info("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

查看控制台输出:

Using orElse
Creating New User
Using orElseGet

这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User对象

在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。所以在使用orElse的时候推荐使用的是orElseGet方法。

不同的原因所在

我们要清楚的是方法中如果是:method(a,b());那么这种情况下,肯定是b()方法先执行的;

而如果是:method(a,()->{}); 那么这种情况就不一样了。所以这里也就是这些导致了根本性原因的地方。

那么既然在orElse中来进行使用了,那么就表明了对应的方法中一定要实现,不管是为空还是不为空,那么就应该来进行实现。

lambda表达式表示的是执行的一段逻辑,而对于普通方法来说就是正常的方法来进行执行。

2.4、返回异常

除了 orElse()orElseGet() 方法,Optional 还定义了 orElseThrow() API —— 它会在对象为空的时候抛出异常,而不是返回备选的值:

@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
    User result = Optional.ofNullable(user)
      .orElseThrow( () -> new IllegalArgumentException());
}

这里,如果 user 值为 null,会抛出 IllegalArgumentException

这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException

现在我们已经很好地理解了如何使用 Optional,我们来看看其它可以对 Optional 值进行转换和过滤的方法。

2.5、转换值

有很多种方法可以转换 Optional 的值。我们从 map()flatMap() 方法开始。

也就是说如果箱子中的东西存在,那么可以让其转换成别的东西,然后继续来进行使用。

@Test
public void whenMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("default@gmail.com"); 
    assertEquals(email, user.getEmail());
}

map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链试调用的操作成为可能 —— 这里的下一环就是 orElse()

相比这下,flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果。

下面的操作中,我们给 User 类添加了一个方法,用来返回 Optional

public class User {    
    private String position;
 
    public Optional<String> getPosition() {
        return Optional.ofNullable(position);
    }
 
    //...
}

既然 getter 方法返回 String 值的 Optional,你可以在对 User 的 Optional 对象调用 flatMap() 时,用它作为参数。其返回的值是解除包装的 String 值:

@Test
public void whenFlatMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    user.setPosition("Developer");
    String position = Optional.ofNullable(user)
      .flatMap(u -> u.getPosition()).orElse("default");
 
    assertEquals(position, user.getPosition().get());
}

2.6、过滤方法

除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。

*filter()* 接受一个 *Predicate* 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional

来看一个根据基本的电子邮箱验证来决定接受或拒绝 User(用户) 的示例:

@Test
public void whenFilter_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    Optional<User> result = Optional.ofNullable(user)
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));
 
    assertTrue(result.isPresent());
}

如果通过过滤器测试,result 对象会包含非空值。

2.7、链式方法

为了更充分的使用 Optional,你可以链接组合其大部分方法,因为它们都返回相同类似的对象。

我们使用 Optional 重写最早介绍的示例。

首先,重构类,使其 getter 方法返回 Optional 引用:

public class User {
    private Address address;
 
    public Optional<Address> getAddress() {
        return Optional.ofNullable(address);
    }
 
    // ...
}
public class Address {
    private Country country;
 
    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }
 
    // ...
}

将之前的操作用Optional中的方法来进行操作:

@Test
public void whenChaining_thenOk() {
    User user = new User("anna@gmail.com", "1234");
 
    String result = Optional.ofNullable(user)
      .flatMap(u -> u.getAddress())
      .flatMap(a -> a.getCountry())
      .map(c -> c.getIsocode())
      .orElse("default");
 
    assertEquals(result, "default");
}

然后将上面的代码进一步进行缩减:

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

参考博客:https://blog.csdn.net/wwe4023/article/details/80760416

3、总结

1、对于OrElse开头的方法来说,如果值为空,那么可以有两种使用方式:

  • 赋予默认值;这里又分为两种情况,上面案例中可以看到orElse和orElseGet是有区别的;
  • 抛出异常(不满足条件);

2、获取得到的Optional对象即使是一个EMPTY对象,也是可以调用当前的实例方法的。

 Optional对象中包装的值才是我们需要保证操作的标准。

1、使用实例一

我们一般在使用Option的时候,通过在保证不为空的时候,可以使用of函数,但是不一定保证的话,那么需要使用ifPresent函数来进行选择性消费,因为这样子来进行操作安全性高一点。

我也自己写了一个例子来进行测试一下:

@Test
void contextLoads() {
    User user = new User();
    String s = Optional.ofNullable(user).map(u -> u.getAddress())
        .map(address -> address.getCountry())
        .map(country -> country.getIsSoCode())
        .orElseGet(()->"code");
    System.out.println(s);
}

全部为空就继续进行消费。

2、使用实例二

在使用的时候,为了防止值为空,需要赋予默认值:

public class DemoOne {
    public static void main(String[] args) {
        User user = new User();
        // 假装这一步我们是不知道的
        user = null;
        System.out.println("main+++++"+Thread.currentThread().getName());
        // 不只是能够对对象来进行操作!还可以操作对象中的属性!但是前提是要保证对象是空的
        user = Optional.<User>ofNullable(user).orElseGet(()->{
            System.out.println("lambda+++++"+Thread.currentThread().getName());
            User user1 = new User();
            System.out.println("ofNullable方法执行了........");
            user1.setName("lig");
            return user1;
        });
        user = null;
        // 保证对象是空的,不如在使用的时候首先来进行判断
        try {
            Integer age = Optional.ofNullable(user)
                    // 这里如果不为空,那么直接获取得到的是里面的T值;如果为空,那么直接在线程栈中抛出异常
                    // 异常有两个作用:1、终止程序运行(不捕捉的情况下);2、抛出异常堆栈信息
                    .orElseThrow(() -> new NullPointerException("user对象为空"))
                    .getAge();
        } catch (NullPointerException e) {
            // 程序既然说是已经运行到了这里,那么就肯定已经符合预期的空指针异常了
            // e.printStackTrace();
        }
        // 注意点:
        // 1、那么这里还可以对这里的age来进行操作!那么是否可以考虑使用Optional<Optional>对象来进行操作
        // 2、如果使用了Optional,可以考虑情况来使用get()方法等等,但是不推荐来进行使用!
    }
}
posted @ 2021-08-27 02:04  写的代码很烂  阅读(1344)  评论(0编辑  收藏  举报