Java8 Optional用法
// Life before Optional private void getIsoCode( User user){ 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的特性
public final class Optional<T> { //Null指针的封装 private static final java.util.Optional<?> EMPTY = new java.util.Optional<>(); //内部包含的值对象 private final T value; private Optional() ; //返回EMPTY对象 public static<T> java.util.Optional<T> empty() ; //构造函数,但是value为null,会报NPE private Optional(T value); //静态工厂方法,但是value为null,会报NPE public static <T> java.util.Optional<T> of(T value); //静态工厂方法,value可以为null public static <T> java.util.Optional<T> ofNullable(T value) ; //获取value,但是value为null,会报NoSuchElementException public T get() ; //返回value是否为null public boolean isPresent(); //如果value不为null,则执行consumer式的函数,为null不做事 public void ifPresent(Consumer<? super T> consumer) ; //过滤,如果value不为null,则根据条件过滤,为null不做事 public java.util.Optional<T> filter(Predicate<? super T> predicate) ; //转换,在其外面封装Optional,如果value不为null,则map转换,为null不做事 public<U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper); //转换,如果value不为null,则map转换,为null不做事 public<U> java.util.Optional<U> flatMap(Function<? super T, java.util.Optional<U>> mapper) ; //value为null时,默认提供other值 public T orElse(T other); //value为null时,默认提供other值 public T orElseGet(Supplier<? extends T> other); //value为null时,默认提供other值 public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) ; }
Optional类提供了大约10种方法,我们可以使用它们来创建和使用Optional类,下面将介绍如何使用它们。
创建一个Optional类
这是用于创建可选实例的三种创建方法。
- static <T> [Optional]<T> [empty]()
返回一个空的Optional实例。
// Creating an empty optional Optional<String> empty = Optional.empty();
在返回一个空的{Optional}实例时,Optional的值不存在。不过,这样做可能很有诱惑力,如果对象为空,请避免与Option.empty()返回的实例的{==}比较 。因为不能保证它是一个单例,反之,应该使用isPresent()。
- static <T> [Optional]<T> [of](T value)
返回特定的非空值Optional。
// Creating an optional using of String name = "java"; Optional<String> opt = Optional.of(name);
静态方法需要一个非null参数;否则,将引发空指针异常。因此,如果我们不知道参数是否为null,那就是我们使用 ofNullable的时候,下面将对此进行介绍。
- static <T> [Optional]<T> [of](T value)
返回描述指定值的Optional,如果非空,则返回空值。
// Possible null value Optional<String> optional = Optional.ofNullable(name()); private String name(){ String name = "Java"; return (name.length() > 5) ? name : null; }
如果我们传入一个空引用,它不会抛出异常,而是返回一个空的Optional对象:
所以这就是动态或手动创建Optional的三种方法。下一组方法用于检查值的存在。
- 布尔值[isPresent]()
如果存在值,则返回true;反之,返回false。如果所包含的对象不为null,则返回true,反之返回false。通常在对对象执行任何其他操作之前,先在Optional上调用此方法。
//ispresent Optional<String> optional1 = Optional.of("javaone"); if (optional1.isPresent()){ //Do something, normally a get }
布尔值[isEmpty()]
如果存在值,则返回false;否则,返回ture。这与isPresent 相反, 并且仅在Java 11及更高版本中可用。
//isempty Optional<String> optional1 = Optional.of("javaone"); if (optional1.isEmpty()){ //Do something }
- void [ifPresent]([Consumer]<? super [T]> consumer)
如果存在值,则使用该值调用指定的使用者;否则,什么都不做。
如果您不熟悉Java 8,那么您可能会想知道:什么是消费者?简单来说,消费者是一种接受参数且不返回任何内容的方法。当使用 ifPresent时,这个方法就是一石二鸟。我们可以执行值存在性检查并使用一种方法执行预期的操作,如下所示。
//ifpresent Optional<String> optional1 = Optional.of("javaone"); optional1.ifPresent(s -> System.out.println(s.length()));
- T[get]()
如果此Optional中存在值,则返回该值,否则抛出 NoSuchElementException。在这之后,我们想要的是存储在Optional中的值,我们可以通过get()来获取它。但是,当该值为null时,此方法将引发异常。这就需要 orElse() 方法来紧急救援。
//get Optional<String> optional1 = Optional.of("javaone"); if (optional1.isPresent()){ String value = optional1.get(); }
- [T ][orElse]([T]其他)
返回值(如果存在);反之,返回其他。
该 orElse() 方法用于检索包装在Optional实例内的值。它采用一个充当默认值的参数。该 orElse() 方法返回包装的值(如果存在)及其参数,反之:
//orElse String nullName = null; String name = Optional.ofNullable(nullName).orElse("default_name");
如果这还不够,那么Optional类将继续提供另一种获取值的方法,即使该方法的null称为 orElseGet()。
- [T][orElseGet]([Supplier]<? extends [T]> other)
返回值(如果存在);否则,调用other并返回该调用的结果。
该orElseGet() 方法类似于 orElse()。但是,如果没有Optional值,则不采用返回值,而是采用供应商功能接口,该接口将被调用并返回调用的值:
//orElseGet String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
那么,orElse() 和orElseGet()之间有什么区别。
乍一看,这两种方法似乎具有相同的效果。但是,事实并非如此。让我们创建一些示例,以突出两者之间的相似性和行为差异。
首先,让我们看看它们在对象为空时的行为:
String text = null; String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultValue); defaultText = Optional.ofNullable(text).orElse(getDefaultValue()); public String getDefaultValue() { System.out.println("Getting Default Value"); return "Default Value"; }
Getting default value... Getting default value...
现在,让我们运行另一个该值存在测试,理想情况下,甚至不应创建默认值:
在这个简单的示例中,创建默认对象不会花费很多成本,因为JVM知道如何处理此类对象。但是,当诸如此类的方法 default 必须进行Web服务调用或者查询数据库时,则成本变得非常明显。
1,创建 Optional 实例
重申一下,这个类型的对象可能包含值,也可能为空。你可以使用同名方法创建一个空的 Optional。
Optional<User> emptyOpt = Optional.empty(); emptyOpt.get();
毫不奇怪,尝试访问 emptyOpt 变量的值会导致 NoSuchElementException。
你可以使用 of() 和 ofNullable() 方法创建包含值的 Optional。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException:
Optional<User> opt = Optional.of(user);
因此,你应该明确对象不为 null 的时候使用 of()。
如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:
Optional<User> opt = Optional.ofNullable(user);
2,访问 Optional 对象的值
从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法:
String name = "John"; Optional<String> opt = Optional.ofNullable(name); assertEquals("John", opt.get());
不过,你看到了,这个方法会在值为 null的时候抛出异常。要避免异常,你可以选择首先验证是否有值:
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 的时候才会执行断言。
接下来,我们来看看提供空值的方法。
3,返回默认值
Optional类提供了API用以返回对象值,或者在对象为空的时候返回默认值:orElse(),
如果有值则返回该值,否则返回传递给它的参数值:
User user2 = new User("anna@gmail.com", "1234"); User result = Optional.ofNullable(user).orElse(user2); assertEquals(user2.getEmail(), result.getEmail());
这里 user 对象是空的,所以返回了作为默认值的 user2。
如果对象的初始值不是 null,那么默认值会被忽略:
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);
4,orElse() 和 orElseGet() 的不同之处
乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。我们创建一些示例来突出二者行为上的异同。
我们先来看看对象为空时他们的行为:
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"); }
上面的代码中,两种方法都调用了 createNewUser() 方法,这个方法会记录一个消息并返回 User 对象。
代码输出如下:
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 服务或数据查询,这个差异会对性能产生重大影响。
5,返回异常
除了 orElse() 和 orElseGet() 方法,Optional 还定义了 orElseThrow() API —— 它会在对象为空的时候抛出异常,而不是返回备选的值:
User result = Optional.ofNullable(user).orElseThrow( () -> new IllegalArgumentException());
这里,如果 user 值为 null,会抛出 IllegalArgumentException。
这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。
现在我们已经很好地理解了如何使用 Optional,我们来看看其它可以对 Optional 值进行转换和过滤的方法。
6,转换值
有很多种方法可以转换 Optional 的值。我们从 map() 和 flatMap() 方法开始。
先来看一个使用 map() API 的例子:
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 值:
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());
7,过滤值
除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。
filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。
来看一个根据基本的电子邮箱验证来决定接受或拒绝 User(用户) 的示例:
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 对象会包含非空值。
8,Optional 类的链式方法
为了更充分的使用 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); } // ... }
现在可以删除 null 检查,替换为 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");
结果现在的代码看起来比之前采用条件分支的冗长代码简洁多了。
四,Java 9 增强
我们介绍了 Java 8 的特性,Java 9 为 Optional 类添加了三个方法:or()、ifPresentOrElse() 和 stream()。
or() 方法与 orElse() 和 orElseGet() 类似,它们都在对象为空的时候提供了替代情况。or() 的返回值是由 Supplier 参数产生的另一个 Optional 对象。
如果对象包含值,则 Lambda 表达式不会执行:
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 的例子:
User user = new User("john@gmail.com", "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。
使用Optional最佳实践
就像编程语言的任何其他功能一样,它可以正确使用或被滥用。为了了解使用Optional类的最佳方法,需要了解以下内容:
1.它解决的问题
Optional的方法是尝试通过增加构建更具表现力的API的可能性来减少Java系统中空指针异常的情况,这些API解释了有时缺少返回值的可能性。
如果从一开始就存在Optional,那么大多数库和应用程序可能会更好地处理缺少的返回值,从而减少了空指针异常的数量以及总体上的错误总数。
2.它不解决的问题
Optional并不意味着是一种避免所有类型的空指针的机制。例如,它仍然必须测试方法和构造函数的强制输入参数。
像使用null时一样,Optional不能帮助传达缺失值的含义。以类似的方式,null可能意味着很多不同的东西(找不到值等),因此缺少Optional值也可以。
该方法的调用方仍然需要检查该方法的JavaDoc以理解缺省选项的含义,以便正确地处理它。
同样,以一种类似的方式,可以将检查的异常捕获在一个空块中,没有什么阻止调用方进行调用 get() 并继续进行。
3.何时使用
Optional的预期用途主要是作为返回类型。获取此类型的实例后,可以提取该值(如果存在)或提供其他行为(如果不存在)。
Optional类的一个非常有用的用例是将其与流或返回Optional值以构建流畅的API的其他方法结合。请参见下面的代码段
User user = users.stream().findFirst().orElse(new User("default", "1234"));
a)不要将其用作类中的字段,因为它不可序列化
如果确实需要序列化包含Optional值的对象,则Jackson库提供了将Optionals视为普通对象的支持。这意味着Jackson将空对象视为空,将具有值的对象视为包含该值的字段。可以在jackson-modules-java8项目中找到此功能。
b)不要将其用作构造函数和方法的参数,因为这会导致不必要的复杂代码。
User user = new User("john@gmail.com", "1234", Optional.empty());
转载链接:https://www.jianshu.com/p/362010f310b9