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

Java8中Optional详解

程序员文章站 2022-03-04 11:02:44
...


Optional的使用

1.前言

public class User {
	private Address address;
	
	public Address getAddress() {
		return address;
	}
}

public class Address {
	private Country country;

	public Country getCountry() {
		return country;
	}
}

public class Country {
	private String cityName;

	public String getCityName() {
		return cityName;
	}
}

在Java 8之前,任何访问对象方法或属性的调用都可能导致NullPointerException:

String cityName = user.getAddress().getCountry().getCityName();

如果需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:

if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String cityName = country.getCityName();
        }
    }
}

这很容易就变得冗长,难以维护。为了简化这个过程,使用Optional类进行操作。

2.Optional介绍

可能包含也可能不包含非null值的容器对象。如果一个值存在,isPresent()将返回true并且get()将返回其值。
提供了依赖于是否存在包含值的其他方法,例如orElse()(如果值不存在则返回默认值)和ifPresent()(如果值存在则执行代码块)。
这是一个基于值的类;在Optional实例上使用标识敏感操作(包括引用相等(==),标识哈希码或同步)可能会产生不可预知的结果,应该避免使用。

序号 方法 & 描述
1 **static Optional empty()**返回空的 Optional 实例。
2 **boolean equals(Object obj)**判断其他对象是否等于 Optional。
3 **Optional filter(Predicate<? super predicate)**如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
4 ** Optional flatMap(Function<? super T,Optional> mapper)**如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
5 **T get()**如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
6 **int hashCode()**返回存在值的哈希码,如果值不存在 返回 0。
7 **void ifPresent(Consumer<? super T> consumer)**如果值存在则使用该值调用 consumer , 否则不做任何事情。
8 **boolean isPresent()**如果值存在则方法会返回true,否则返回 false。
9 **Optional map(Function<? super T,? extends U> mapper)**如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
10 **static Optional of(T value)**返回一个指定非null值的Optional。
11 **static Optional ofNullable(T value)**如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
12 **T orElse(T other)**如果存在该值,返回值, 否则返回 other。
13 **T orElseGet(Supplier<? extends T> other)**如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。
14 ** T orElseThrow(Supplier<? extends X> exceptionSupplier)**如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
15 **String toString()**返回一个Optional的非空字符串,用来调试
修饰符和类型 方法和描述
static <T> Optional<T> empty()返回一个空Optional实例。
boolean equals(Object obj)指示某个其他对象是否“等于”此Optional。
Optional<T> filter(Predicate<? super T> predicate)如果存在值,并且值与给定谓词匹配,则返回Optional描述该值的值,否则返回空值Optional
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)如果存在值,则将提供的Optional-bearing mapping函数应用于该值,返回该结果,否则返回空 Optional
T get()如果此值中存在Optional值,则返回该值,否则抛出NoSuchElementException
int hashCode()返回当前值的哈希码值(如果有),如果不存在值,则返回0(零)。
void ifPresent(Consumer<? super T> consumer)如果存在值,则使用值调用指定的使用者,否则不执行任何操作。
boolean isPresent()``true如果存在值则返回,否则返回false
<U> Optional<U> map(Function<? super T,? extends U> mapper)如果存在值,则将提供的映射函数应用于该值,如果结果为非null,则返回Optional描述结果的值。
static <T> Optional<T> of(T value)返回Optional具有指定的当前非null值的a。
static <T> Optional<T> ofNullable(T value)返回Optional描述指定值的值,如果为非null,否则返回空值Optional
T orElse(T other)如果存在则返回值,否则返回other
T orElseGet(Supplier<? extends T> other)返回值(如果存在),否则调用other并返回该调用的结果。
<X extends Throwable>T orElseThrow(Supplier<? extends X> exceptionSupplier)返回包含的值(如果存在),否则抛出由提供的供应商创建的异常。
String toString()返回此Optional的非空字符串表示形式,适用于调试。

3.创建Optional

3.1empty

返回一个空Optional实例。此Optional没有值。

注意: 尽管这样做很有诱惑力,但是要避免测试对象是否为空通过将==Option.empty()返回的实例进行比较。不能保证它是单例的。使用isPresent()替代。

即:通过isPresent()判断对象是否为空。

Optional<User> empty = Optional.empty();

3.2of

返回具有指定的当前非空值的Optional。如果值为null则抛出NullPointerException

Optional<User> userOptional = Optional.of(null);

3.3ofNullable

如果为非null,返回描述指定值的Optional,否则返回空Optional。

如果对象可能是null也可能是非null,应该使用ofNullable()。

Optional<User> userOptional = Optional.ofNullable(null);

4.获取Optional的值

4.1get()

如果此Optional中存在值,则返回该值,否则抛出NoSuchElementException

Optional<User> userOptional = Optional.ofNullable(null);
if (userOptional.isPresent()) {
	User user = userOptional.get();
}

5.检查Optional是否有值

5.1isPresent()

如果存在值,则返回true,否则返回false。

Optional<User> userOptional = Optional.ofNullable(null);
boolean isPresent = userOptional.isPresent();

5.2ifPresent()

如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。

Optional<User> userOptional = Optional.ofNullable(null);
userOptional.ifPresent(user -> System.out.println(user.getAddress()));

6.filter

如果存在值,并且值与给定谓词匹配,则返回描述该值的Optional,否则返回空Optional

Optional<User> userOptional = Optional.ofNullable(null);
Optional<User> result = userOptional.filter(user -> user.getAddress() != null);

7.map

如果存在值,则将提供的映射函数应用于该值,如果结果为非null,则返回描述结果的Optional。否则返回空Optional

注意: 此方法支持对optional值进行后处理,而无需显式检查返回状态。例如,以下代码遍历文件名流,选择一个尚未处理的文件名,然后打开该文件,返回 Optional<FileInputStream>

Optional<FileInputStream> fis = names.stream()
									.filter(name -> !isProcessedYet(name))
									.findFirst()
									.map(name -> new FileInputStream(name));

在这里,findFirst返回一个Optional<String>,然后map返回一个所需文件的Optional<FileInputStream>(如果存在)。

Optional<User> userOptional = Optional.ofNullable(null);
Optional<Address> address = userOptional.map(User::getAddress);
Optional<Country> country = address.map(Address::getCountry);

使用map进行链式调用

String result = Optional.ofNullable(new User())
                .map(User::getAddress)
                .map(Address::getCountry)
                .map(Country::getCityName)
                .orElse("上海");
System.out.println(result);

// 上海

如果属性值以Optional封装

public class User {
    private Optional<Address> address;

    public Optional<Address> getAddress() {
        return address;
    }
}

public class Address {
    private Optional<Country> country;

    public Optional<Country> getCountry() {
        return country;
    }
}

public class Country {
    private String cityName;

    public String getCityName() {
        return cityName;
    }
}
Optional<User> userOptional = Optional.ofNullable(null);
Optional<Optional<Address>> address = userOptional.map(User::getAddress);

可以看出,map返回的是包装原结果类型的Optional类型。

8.flatMap

如果存在值,则将提供的Optional-bearing映射函数应用于该值,返回该结果,否则返回空Optional。这个方法类似于map(Function),但提供的映射器是一个结果已经是Optional的映射器,如果被调用,flatMap不会附加一个Optional

注意:和map的区别是不会附加一个Optional

Optional<String> stringOptional = Optional.ofNullable("hello")
		.flatMap(a -> Optional.ofNullable(a + " world!"));
System.out.println(stringOptional.get());

// hello world!

如果属性值以Optional封装(参见map)

User user = null;
String cityName = Optional.ofNullable(user).flatMap(User::getAddress)
        .flatMap(Address::getCountry)
        .map(Country::getCityName).orElse("上海");
System.out.println(cityName);

// 上海

值得注意的是,一些博客并没有很详细的说明flatMap和map到底有什么区别,上面的例子如果改成这样

User user = new User();
String cityName = Optional.ofNullable(user)
		.flatMap(User::getAddress)
        .flatMap(Address::getCountry)
        .map(Country::getCityName).orElse("上海");
System.out.println(cityName);

会报空指针异常Exception in thread "main" java.lang.NullPointerException

对比map和flatMap的源码,map返回的是Optional.ofNullable包装的U,而flatMap返回的是Objects.requireNonNull包装的Optional<U>,如果mapper.apply(value)的执行结果为null,那么flatMap就会抛出NullPointerException

上面的例子也验证了这一点,而且使用flatMap还需要改变原有实体类属性的类型,不符合编程习惯不说,并不能解决链式调用中某一环节出现NullPointerException的情况。

所以,推荐使用map处理链式调用的情况

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        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 {
        return Objects.requireNonNull(mapper.apply(value));
    }
}

9.返回默认值

9.1orElse

如果存在则返回值,否则返回other

String s = null;
String str = Optional.ofNullable(s).orElse("default");
System.out.println(str);

// default

9.2orElseGet

如果存在则返回值,否则调用other并返回该调用的结果。

注意: 如果值不存在且other为null则抛出NullPointerException

String s = null;
String s1 = Optional.ofNullable(s).orElseGet(() -> null);
System.out.println(s1);
String s2 = Optional.ofNullable(s).orElseGet(null);
System.out.println(s2);

// null
// Exception in thread "main" java.lang.NullPointerException

9.3orElseThrow

如果存在返回包含的值,否则抛出由提供的供应商创建的异常。

注意: 具有空参数列表的异常构造函数的方法引用可以用作供应商。例如, IllegalStateException::new

String orElseThrow = Optional.ofNullable(s).orElseThrow(IllegalArgumentException::new);
System.out.println(orElseThrow);

这个方法丰富了语义,可以决定抛出什么样的异常。

10.orElse和orElseGet的不同之处

对象为空时两者的行为:

private static User createNewUser() {
    System.out.println("Creating New User");
    return new User();
}
    
User user = null;
System.out.println("Using orElse");
User result1 = Optional.ofNullable(user).orElse(createNewUser());
System.out.println("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());

// Using orElse
// Creating New User
// Using orElseGet
// Creating New User

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

对象不为空时两者的行为:

User user = new User();
System.out.println("Using orElse");
User result1 = Optional.ofNullable(user).orElse(createNewUser());
System.out.println("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());

// Using orElse
// Creating New User
// Using orElseGet

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

private static String getString() {
    System.out.println("调用我了");
    return "bbb";
}

String s1 = Optional.ofNullable("aaa").orElse(getString());
System.out.println(s1);

// 调用我了
//aaa

在执行较密集的调用时,比如调用Web服务或数据查询,这个差异会对性能产生重大影响

11.Java9增强

Java 9为Optional类增加了三个方法:or()、ifPresentOrElse()和stream()。

or()方法与orElse()和orElseGet()类似,它们都在对象为空的时候提供了替代情况。or() 的返回值是由Supplier参数产生的另一个Optional对象。

如果对象包含值,则Lambda表达式不会执行:

@Test
public void whenEmptyOptional_thenGetValueFromOr() {
    User result = Optional.ofNullable(user)
      .or( () -> Optional.of(new User("default","1234"))).get();

    assertEquals(result.getEmail(), "default");
}

上面的示例中,如果 user 变量是 null,它会返回一个 Optional,它所包含的 User 对象,其电子邮件为 “default”。

ifPresentOrElse() 方法需要两个参数:一个 Consumer 和一个 Runnable。如果对象包含值,会执行 Consumer 的动作,否则运行 Runnable。
如果你想在有值的时候执行某个动作,或者只是跟踪是否定义了某个值,那么这个方法非常有用:

Optional.ofNullable(user).ifPresentOrElse( u -> logger.info("User is:" + u.getEmail()),
  () -> logger.info("User not found"));

最后介绍的是新的 stream() 方法,它通过把实例转换为 Stream 对象,让你从广大的 Stream API 中受益。如果没有值,它会得到空的 Stream;有值的情况下,Stream 则会包含单一值。

我们来看一个把 Optional 处理成 Stream 的例子:

@Test
public void whenGetStream_thenOk() {
    User user = new User("[email protected]", "1234");
    List<String> emails = Optional.ofNullable(user)
      .stream()
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
      .map( u -> u.getEmail())
      .collect(Collectors.toList());

    assertTrue(emails.size() == 1);
    assertEquals(emails.get(0), user.getEmail());
}

这里对 Stream 的使用带来了其 filter()、map() 和 collect() 接口,以获取 List。

本文参考:
理解、学习与使用 JAVA 中的 OPTIONAL
Optional (Java Platform SE 8 )