Optional 是一个容器对象:要么包含一个非 null 的值,要么为空(empty)。有值时 isPresent() 返回 true、get() 返回该值;为空时 isPresent() 返回 false。 它不保存 null ——容器内部的 null 只表示「没有值」(Optional.empty() 与 ofNullable(null) 拿到的都是它);Optional 变量本身也永远不该是 null,这是官方 API Note 明确要求的。 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。
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和一个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(),区别是值不存在时接收的不是现成的默认值,而是一个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()的区别
orElse()和orElseGet()看起来功能重叠,很多刚接触Optional或 Java 8 的人也分不清两者的区别。 两者差别很小,但用错了会明显影响性能。
在测试类里加一个 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(),行为完全相同。
再跑一个「值存在」的测试,这时连默认值都不应该被创建:
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()不管包装值是否存在,都会先把默认对象创建出来,值存在时这个对象就白建了。
这个例子里,创建一个默认对象成本很低。然而,当像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时返回值,否则抛 NoSuchElementException:
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; } }
Modem 只有一个字段:价格。下面先看没有 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可以链在一起做更复杂的转换。
假设要检查用户输入的密码是否正确:先用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()转换值 flatMap() 与 map() 的差别在于映射结果还要不要再包一层 :map() 会把映射结果按 ofNullable() 包起来,所以映射函数本身返回 Optional 时会得到嵌套的 Optional<Optional<T>>;flatMap() 要求映射函数直接返回 Optional,也不会再包装,嵌套因此被摊平。边界也别记错:map() 的映射函数返回 null 只会得到空 Optional,flatMap() 的映射函数返回 null 直接抛 NullPointerException(上面示例里的 Person::getName 返回的就是 Optional<String>)。 前面包装的都是String和Integer这样的简单对象,实际代码里的值则常常来自复杂对象的访问器。
包装一个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) Stream API 本来是给集合用的,Java 9 给 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():配一个 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 给Optional加了stream()方法之后,一行就够了:
1 2 3 List<String> filteredList = listOfOptionals.stream() .flatMap(Optional::stream) .collect(Collectors.toList());
多个Stream链接Optionals
想从多个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); }
用Stream API 串联几个Optional,取第一个非空值:
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方法都会被执行,不管非空的那个出现在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); }
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() 是并列的方法,区别在值缺失时的行为——它不返回默认值,而是抛异常:传异常构造器的那个重载(Java 8 起)抛自定义异常,无参版本(Java 10 起)抛 NoSuchElementException:
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 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 作为方法参数传递。 假设有一个 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 );
对方照着调用,传了 null,直接拿到NullPointerException。 结果还是得给这个「可选参数」做 null 检查,跟最初想避免 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()); }
总结 Java 8 Optional类的重要特性基本都在上面。 用Optional而不是显式 null 检查,理由前面给过几个。 取值用get()、orElse()、orElseGet(),值为空时给默认值。 转换和过滤用 map()、flatMap()、filter(),这些方法可以直接链起来。
参考资料
系列索引:Java 系列 ,语言特性与运行时的长文集