Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。 Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检> 测。 Optional 类的引入很好的解决空指针异常。
1 2 3 4 5 6 7 8 public class Person { private String name; private int age; private String password; private List<String> hobbies; }
创建Optional对象 使用empty()方法创建空的Optional对象 1 2 3 4 5 @Test public void whenCreatesEmptyOptional_thenCorrect () { Optional<String> empty = Optional.empty(); assertFalse(empty.isPresent()); }
isPresent() 用来检查Optional对象中是否有值,仅当我们使用非空值创建Optional时才会存在值
of()方法创建Optional对象
我们可以使用静态方法 of()创建一个Optional对象:
1 2 3 4 5 6 @Test public void givenNonNull_whenCreatesNonNullable_thenCorrect () { String name = "baeldung" ; Optional<String> opt = Optional.of(name); assertTrue(opt.isPresent()); }
传递给of()方法的参数不能为空。否则,我们将得到一个NullPointerException:
1 2 3 4 5 @Test(expected = NullPointerException.class) public void givenNull_whenThrowsErrorOnCreate_thenCorrect () { String name = null ; Optional.of(name); }
ofNullable()创建可能是空值的Optional对象 1 2 3 4 5 6 @Test public void givenNonNull_whenCreatesNullable_thenCorrect () { String name = "baeldung" ; Optional<String> opt = Optional.ofNullable(name); assertTrue(opt.isPresent()); }
通过这样做,如果我们传入一个空引用,它不会抛出异常,而是返回一个空的Optional对象:
1 2 3 4 5 6 @Test public void givenNull_whenCreatesNullable_thenCorrect () { String name = null ; Optional<String> opt = Optional.ofNullable(name); assertFalse(opt.isPresent()); }
Optional中基本函数的使用 检查Optional值是否存在
使用 isPresent() 方法可以检查Optional对象中是否有值.
1 2 3 4 5 6 7 8 @Test public void givenOptional_whenIsPresentWorks_thenCorrect () { Optional<String> opt = Optional.of("Baeldung" ); assertTrue(opt.isPresent()); opt = Optional.ofNullable(null ); assertFalse(opt.isPresent()); }
如果包装值不为空,则此方法返回true 。
我们可以使用 isEmpty 方法执行相反的操作:
1 2 3 4 5 6 7 8 @Test public void givenAnEmptyOptional_thenIsEmptyBehavesAsExpected () { Optional<String> opt = Optional.of("Baeldung" ); assertFalse(opt.isEmpty()); opt = Optional.ofNullable(null ); assertTrue(opt.isEmpty()); }
ifPresent()条件动作
ifPresent ()方法使我们能够在包装值被发现为非null时运行一些代码。在Optional之前,我们会做:
1 2 3 if (name != null ) { System.out.println(name.length()); }
此代码在继续对其执行某些代码之前检查 name 变量是否为null 。这种方法很冗长,而且这不是唯一的问题——它也容易出错。
Optional使我们能够显式地处理可为 null 的值,以此作为强制执行良好编程实践的一种方式。
1 2 3 4 5 @Test public void givenOptional_whenIfPresentWorks_thenCorrect () { Optional<String> opt = Optional.of("baeldung" ); opt.ifPresent(name -> System.out.println(name.length())); }
1 2 3 4 5 6 7 8 9 10 private void optionalOnlyElseMethodDemo () { var person = new Person (); person.setName("test" ); if (null != person) { System.out.println("Hello " + person.getName()); } Optional.ofNullable(person) .ifPresent(value -> System.out.println("Hello " + value.getName())); }
ifPresentOrElse()方法的使用
当我们有一个Optional实例时,我们通常希望对它的基础值执行特定的操作。另一方面,如果Optional为空,我们希望记录它或通过增加一些指标来跟踪该事实。
ifPresentOrElse ()方法正是为这种情况而创建的。我们可以传递一个Consumer ,如果定义了 Optional 将被调用,如果Optional为空则将执行Runnable 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void givenOptional_whenPresent_thenShouldExecuteProperCallback () { Optional<String> value = Optional.of("properValue" ); AtomicInteger successCounter = new AtomicInteger (0 ); AtomicInteger onEmptyOptionalCounter = new AtomicInteger (0 ); value.ifPresentOrElse( v -> successCounter.incrementAndGet(), onEmptyOptionalCounter::incrementAndGet); assertThat(successCounter.get()).isEqualTo(1 ); assertThat(onEmptyOptionalCounter.get()).isEqualTo(0 ); }
请注意,作为第二个参数传递的回调未执行。
在Optional为空的情况下,将执行第二个回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void givenOptional_whenNotPresent_thenShouldExecuteProperCallback () { Optional<String> value = Optional.empty(); AtomicInteger successCounter = new AtomicInteger (0 ); AtomicInteger onEmptyOptionalCounter = new AtomicInteger (0 ); value.ifPresentOrElse( v -> successCounter.incrementAndGet(), onEmptyOptionalCounter::incrementAndGet); assertThat(successCounter.get()).isEqualTo(0 ); assertThat(onEmptyOptionalCounter.get()).isEqualTo(1 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void optionalIfElseMethodDemo () { var person = new Person (); person.setName("test" ); if (null != person) { System.out.println("Hello " + person.getName()); } else { System.out.println("World" ); } Optional.ofNullable(person) .ifPresentOrElse(value -> System.out.println("Hello " + value.getName()), () -> System.out.println("World" )); }
orElse() 给Optional赋默认值
orElse ()方法用于检索包装在Optional实例中的值。它采用一个参数,作为默认值。orElse ()方法返回包装值(如果存在),否则返回其参数:
1 2 3 4 5 6 @Test public void whenOrElseWorks_thenCorrect () { String nullName = null ; String name = Optional.ofNullable(nullName).orElse("john" ); assertEquals("john" , name); }
orElseGet() 的默认值
orElseGet () 方法类似于orElse()。但是,如果Optional值不存在,它不会采用返回值,而是采用supplier functional interface,该接口被调用并返回调用的值
1 2 3 4 5 6 @Test public void whenOrElseGetWorks_thenCorrect () { String nullName = null ; String name = Optional.ofNullable(nullName).orElseGet(() -> "john" ); assertEquals("john" , name); }
对比orElse和orElseGet()的区别
对于很多刚接触Optional或 Java 8 的程序员来说,orElse()和orElseGet()之间的区别并不清楚。事实上,这两种方法给人的印象是它们在功能上相互重叠。 然而,两者之间有一个微妙但非常重要的区别,如果没有很好地理解,可能会极大地影响我们代码的性能。
让我们在测试类中创建一个名为getMyDefault()的方法,它不接受任何参数并返回一个默认值:
1 2 3 4 public String getMyDefault () { System.out.println("Getting Default Value" ); return "Default Value" ; }
让我们看两个测试并观察它们的副作用以确定orElse() 和orElseGet()重叠的地方以及它们不同的地方:
1 2 3 4 5 6 7 8 9 10 @Test public void whenOrElseGetAndOrElseOverlap_thenCorrect () { String text = null ; String defaultText = Optional.ofNullable(text).orElseGet(this ::getMyDefault); assertEquals("Default Value" , defaultText); defaultText = Optional.ofNullable(text).orElse(getMyDefault()); assertEquals("Default Value" , defaultText); }
在上面的示例中,我们将一个空文本包装在一个Optional对象中,并尝试使用两种方法中的每一种来获取包装后的值。
1 2 Getting default value... Getting default value...
在每种情况下都会调用getMyDefault()方法。碰巧当包装值不存在时,orElse()和orElseGet()的工作方式完全相同.
现在让我们运行另一个存在值的测试,理想情况下,甚至不应该创建默认值:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void whenOrElseGetAndOrElseDiffer_thenCorrect () { String text = "Text present" ; System.out.println("Using orElseGet:" ); String defaultText = Optional.ofNullable(text).orElseGet(this ::getMyDefault); assertEquals("Text present" , defaultText); System.out.println("Using orElse:" ); defaultText = Optional.ofNullable(text).orElse(getMyDefault()); assertEquals("Text present" , defaultText); }
在上面的示例中,我们不再包装空值,其余代码保持不变。现在让我们看一下运行这段代码的副作用:
1 2 3 Using orElseGet: Using orElse: Getting default value...
当使用orElseGet()检索包装值时,甚至不会调用getMyDefault()方法,因为包含的值存在。 但是,当使用orElse()时,无论包装值是否存在,都会创建默认对象。所以在这种情况下,我们只是创建了一个从未使用过的冗余对象。
在这个简单的例子中,创建一个默认对象并没有太大的成本,因为 JVM 知道如何处理这样的事情。然而,当像getMyDefault()这样的方法必须进行 Web 服务调用甚至查询数据库时,成本就变得非常明显。
使用get()获取返回值
1 2 3 4 5 6 @Test public void givenOptional_whenGetsValue_thenCorrect () { Optional<String> opt = Optional.of("baeldung" ); String name = opt.get(); assertEquals("baeldung" , name); }
但是,与前面三种方法不同,get()只能在包装对象不为null的情况下返回一个值;否则,它会抛出一个没有这样的元素异常:
1 2 3 4 5 @Test(expected = NoSuchElementException.class) public void givenOptionalWithNull_whenGetThrowsException_thenCorrect () { Optional<String> opt = Optional.ofNullable(null ); String name = opt.get(); }
Optional中高级函数的使用 filter()条件过滤 1 2 3 4 5 6 7 8 9 @Test public void whenOptionalFilterWorks_thenCorrect () { 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); }
例子
我们从某个站点接收有关调制解调器价格的推送通知,并将其存储在对象中:
1 2 3 4 5 6 public class Modem { private Double price; public Modem (Double price) { this .price = price; } }
其唯一目的是检查调制解调器价格是否在我们的预算范围内, 现在让我们看一下没有Optional的代码:
1 2 3 4 5 6 7 8 9 10 11 public boolean priceIsInRange1 (Modem modem) { boolean isInRange = false ; if (modem != null && modem.getPrice() != null && (modem.getPrice() >= 10 && modem.getPrice() <= 15 )) { isInRange = true ; } return isInRange; }
使用 Optional#filter()之后的变体
1 2 3 4 5 6 7 public boolean priceIsInRange2 (Modem modem2) { return Optional.ofNullable(modem2) .map(Modem::getPrice) .filter(p -> p >= 10 ) .filter(p -> p <= 15 ) .isPresent(); }
map()转换Optional
1 2 3 4 5 6 7 8 9 10 11 @Test public void givenOptional_whenMapWorks_thenCorrect () { List<String> companyNames = Arrays.asList( "paypal" , "oracle" , "" , "microsoft" , "" , "apple" ); Optional<List<String>> listOptional = Optional.of(companyNames); int size = listOptional .map(List::size) .orElse(0 ); assertEquals(6 , size); }
我们可以将 map和filter链接在一起以做更强大的事情。
假设我们要检查用户输入的密码的正确性。我们可以使用映射转换清理密码并使用过滤器检查其正确性:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void givenOptional_whenMapWorksWithFilter_thenCorrect () { String password = " password " ; 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()转换值 就像map()方法一样,我们也有flatMap()方法作为转换值的替代方法。不同之处在于map仅在解包时才对值进行转换,而flatMap采用包装值并在转换之前对其进行解包。 之前,我们创建了简单的String和Integer对象来包装在Optional实例中。但是,我们经常会从复杂对象的访问者那里接收到这些对象。
当我们包装一个Person对象时,它将包含嵌套的Optional实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void givenOptional_whenFlatMapWorks_thenCorrect2 () { Person person = new Person ("john" , 26 ); Optional<Person> personOptional = Optional.of(person); Optional<Optional<String>> nameOptionalWrapper = personOptional.map(Person::getName); Optional<String> nameOptional = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new ); String name1 = nameOptional.orElse("" ); assertEquals("john" , name1); String name = personOptional .flatMap(Person::getName) .orElse("" ); assertEquals("john" , name); }
or()方法去除多重判空 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public String optionalOrMethodDemo () { var nullPerson = null ; if (null == nullPerson) { Person person1 = new Person (); person1.setName("test1" ); if (null == person1) { Person person2 = new Person (); person2.setName("test2" ); return person2.getName(); } return person1.getName(); } var result = Optional.ofNullable(nullPerson) .or(() -> { Person person1 = new Person (); person1.setName("test1" ); return Optional.ofNullable(person1); }) .or(() -> { Person person2 = new Person (); person2.setName("test2" ); return Optional.ofNullable(person2); }) .map(Person::getName) .orElse("name" ); }
Optional-Stream() (JDK9) Java 有一个非常流畅和优雅的Stream API,可以对集合进行操作并利用许多函数式编程概念。最新的 Java 版本在Optional类上引入了stream()方法,允许我们将Optional实例视为Stream
基础使用
已定义的Optional,我们正在调用它的stream()方法。这将创建一个包含一个元素的Stream ,我们可以在其上使用Stream API中可用的所有方法:
1 2 3 4 5 6 7 8 9 10 11 @Test public void givenOptionalOfSome_whenToStream_thenShouldTreatItAsOneElementStream () { Optional<String> value = Optional.of("a" ); List<String> collect = value.stream().map(String::toUpperCase).collect(Collectors.toList()); assertThat(collect).hasSameElementsAs(List.of("A" )); }
另一方面,如果Optional不存在,调用它的stream()方法将创建一个空的Stream:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void givenOptionalOfNone_whenToStream_thenShouldTreatItAsZeroElementStream () { Optional<String> value = Optional.empty(); List<String> collect = value.stream() .map(String::toUpperCase) .collect(Collectors.toList()); assertThat(collect).isEmpty(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void optionalStreamMethodDemo () { var person = new Person (); person.setName("Test" ); var list = Arrays.asList("eat" , "sleep" , "dadoudou" ); person.setHobbies(list); Optional.of(person) .map(Person::getHobbies) .orElse(Collections.emptyList()) .stream() .filter(value -> value.equals("eat" )) .toList() .forEach(System.out::println); Optional.of(person) .map(Person::getHobbies) .stream() .flatMap(Collection::stream) .filter(value -> value.equals("eat" )) .toList() .forEach(System.out::println); }
过滤Optional-Stream流
1 2 List<Optional<String>> listOfOptionals = Arrays.asList( Optional.empty(), Optional.of("foo" ), Optional.empty(), Optional.of("bar" ));
使用 filter(), Java 8 中的一个选项是使用Optional::isPresent过滤掉值,然后使用Optional::get函数执行映射以提取值:
1 2 3 4 List<String> filteredList = listOfOptionals.stream() .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList());
flatMap(), 将flatMap与 lambda 表达式一起使用,该表达式将空Optional转换为空Stream实例,并将非空Optional转换为仅包含一个元素的Stream实例:
1 2 3 4 5 6 7 8 List<String> filteredList = listOfOptionals.stream() .flatMap(o -> o.isPresent() ? Stream.of(o.get()):Stream.empty()) .collect(Collectors.toList()); List<String> filteredList = listOfOptionals.stream() .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty)) .collect(Collectors.toList());
Optional::stream, 随着 Java 9 的到来,将stream()方法添加到Optional中,所有这些都将变得非常简单
1 2 3 List<String> filteredList = listOfOptionals.stream() .flatMap(Optional::stream) .collect(Collectors.toList());
多个Stream链接Optionals
我们可能需要从多个Optional中获取第一个非空Optional对象。在这种情况下,使用像orElseOptional()这样的方法会非常方便。不幸的是,Java 8 不直接支持这样的操作。
让我们首先介绍一些我们将在本节中使用的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private Optional<String> getEmpty () { return Optional.empty(); } private Optional<String> getHello () { return Optional.of("hello" ); } private Optional<String> getBye () { return Optional.of("bye" ); } private Optional<String> createOptional (String input) { if (input == null || "" .equals(input) || "empty" .equals(input)) { return Optional.empty(); } return Optional.of(input); }
为了链接几个Optional对象并获得 Java 8 中的第一个非空对象,我们可以使用Stream API:
1 2 3 4 5 6 7 8 9 @Test public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturned () { Optional<String> found = Stream.of(getEmpty(), getHello(), getBye()) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); assertEquals(getHello(), found); }
这种方法的缺点是我们所有的get方法总是被执行,无论非空Optional出现在Stream中的什么地方。
如果我们想延迟评估传递给Stream.of()的方法,我们需要使用方法引用和Supplier接口:
1 2 3 4 5 6 7 8 9 10 11 @Test public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturnedAndRestNotEvaluated () { Optional<String> found = Stream.<Supplier<Optional<String>>>of(this ::getEmpty, this ::getHello, this ::getBye) .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); assertEquals(getHello(), found); }
如果我们需要使用带参数的方法,我们必须求助于 lambda 表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void givenTwoOptionalsReturnedByOneArgMethod_whenChaining_thenFirstNonEmptyIsReturned () { Optional<String> found = Stream.<Supplier<Optional<String>>>of( () -> createOptional("empty" ), () -> createOptional("hello" ) ) .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); assertEquals(createOptional("hello" ), found); }
通常,我们希望返回一个默认值,以防所有链接的Optional都为空。我们只需添加对orElse()或orElseGet()的调用即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void givenTwoEmptyOptionals_whenChaining_thenDefaultIsReturned () { String found = Stream.<Supplier<Optional<String>>>of( () -> createOptional("empty" ), () -> createOptional("empty" ) ) .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst() .orElseGet(() -> "default" ); assertEquals("default" , found); }
异常处理 orElseThrow()异常
orElseThrow() 方法继承自 orElse() 和 orElseGet() 并添加了一种处理缺失值的新方法。当包装值不存在时,它不会返回默认值,而是抛出异常:
1 2 3 4 5 6 @Test(expected = IllegalArgumentException.class) public void whenOrElseThrowWorks_thenCorrect () { String nullName = null ; String name = Optional.ofNullable(nullName).orElseThrow( IllegalArgumentException::new ); }
Java 8 中的方法引用在这里派上用场,传递异常构造函数。
Java 10 引入了一个简化的无参数版本的orElseThrow()方法。如果是空Optional,它会抛出NoSuchElementException:
1 2 3 4 5 @Test(expected = NoSuchElementException.class) public void whenNoArgOrElseThrowWorks_thenCorrect () { String nullName = null ; String name = Optional.ofNullable(nullName).orElseThrow(); }
滥用Optional 最后,让我们看看使用Optional的一种诱人但危险的方法:将 Optional参数传递给方法。 假设我们有一个 Person列表,我们想要一个方法来搜索该列表中具有给定名称的人。此外,我们希望该方法匹配至少具有特定年龄的条目(如果已指定)。 由于此参数是可选的,我们使用此方法:
1 2 3 4 5 6 7 public static List<Person> search (List<Person> people, String name, Optional<Integer> age) { return people.stream() .filter(p -> p.getName().equals(name)) .filter(p -> p.getAge().get() >= age.orElse(0 )) .collect(Collectors.toList()); }
然后我们把这个方法给另一个开发者尝试使用它:
1 someObject.search(people, "Peter" , null );
现在开发人员执行其代码并获得NullPointerException。 我们不得不对我们的可选参数进行 null 检查,这违背了我们想要避免这种情况的最初目的。
1 2 3 4 5 6 7 8 9 public static List<Person> search (List<Person> people, String name, Integer age) { final Integer ageFilter = age != null ? age : 0 ; return people.stream() .filter(p -> p.getName().equals(name)) .filter(p -> p.getAge().get() >= ageFilter) .collect(Collectors.toList()); }
另一种可能性是创建两个重载方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static List<Person> search (List<Person> people, String name) { return doSearch(people, name, 0 ); } public static List<Person> search (List<Person> people, String name, int age) { return doSearch(people, name, age); } private static List<Person> doSearch (List<Person> people, String name, int age) { return people.stream() .filter(p -> p.getName().equals(name)) .filter(p -> p.getAge().get().intValue() >= age) .collect(Collectors.toList()); }
通过这种方式,我们提供了一个清晰的 API,其中有两种方法可以做不同的事情(尽管它们共享实现)
总结 在本文中,我们介绍了 Java 8 Optional类的大部分重要特性。 我们简要探讨了为什么我们会选择使用Optional而不是显式 null 检查和输入验证的一些原因。 我们还学习了如何使用get()、orElse()和orElseGet()方法获取Optional的值,或者如果为空则获取默认值 然后我们看到了如何使用 map()、flatMap()和 filter()转换或过滤我们的Optional。我们讨论了流畅的API Optional提供了什么,因为它允许我们轻松地链接不同的方法。
参考资料