Optional 类是 Java8 引入的一个容器对象,它可能包含也可能不包含非 null 的值(即 Optional 中可能有值,也可能没有值)。Optional 类的引入是为了解决空指针异常(NullPointerException)的问题,它提供了一种更好的方式来处理可能为 null 的值。
Optional 类的主要优点:
明确表达了代码中可能存在空值的情况,增强了代码的可读性和可理解性。例如,以前可能需要通过大量的 if (obj!= null) 这样的条件判断来处理空值,使用 Optional 后可以更清晰地表明对可能为空值的处理意图。
帮助避免空指针异常。若在未正确处理空值的情况下进行操作,很容易引发 NullPointerException 。Optional 类通过提供一系列方法,使得在处理空值时更加安全。
假如有如下代码:
Map<String,String> map = new HashMap<>(); map.put("key1", null); map.put("key2", "value2"); // 使用 if 语句避免为 null,出现 NullPointerException 异常 if(null != map.get("key1")) { System.out.println("value is " + map.get("key1").toUpperCase()); } if(null != map.get("key2")) { System.out.println("value is " + map.get("key2").toUpperCase()); } //输出: //value is VALUE2
当我们有了 Optional 类,就可以避免上面的 if 语句,修改后的代码如下:
Map<String,String> map = new HashMap<>(); map.put("key1", null); map.put("key2", "value2"); // 使用 Optional 避免 if 语句 Optional.ofNullable(map.get("key1")).ifPresent(v -> { System.out.println("value is "+ v.toUpperCase()); }); Optional.ofNullable(map.get("key2")).ifPresent(v -> { System.out.println("value is "+ v.toUpperCase()); }); //输出: //value is VALUE2
注意,如果 Optional 中存在被封装的对象,那么 get 方法会返回该对象,否则会抛出一个 NoSuchElementException 异常。如下:
Map<String,String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", null); // 使用 Optional 避免 if 语句 String val = Optional.ofNullable(map.get("key1")).get(); System.out.println("value is " + val.toUpperCase()); val = Optional.ofNullable(map.get("key2")).get(); System.out.println("value is " + val.toUpperCase()); //输出: //value is VALUE1 //Exception in thread "main" java.util.NoSuchElementException: No value present // at java.util.Optional.get(Optional.java:135) // at com.hxstrive.jdk8.optional.OptionalDemo3.main(OptionalDemo3.java:22)
Optional 类提供了如下三个方法去创建一个 Optional 对象:
使用 Optional.of(T value) 创建一个包含非 null 值的 Optional 实例,即参数 value 不能为 null。
使用 Optional.empty() 创建一个空的 Optional 实例。
使用 Optional.ofNullable(T value) 创建一个可能包含 null 的 Optional 实例,即参数 value 可以为 null。
示例:
Optional.empty(); // ok Optional.ofNullable("a"); // ok Optional.ofNullable(null); // ok Optional.of("key"); // ok Optional.of(null); // error java.lang.NullPointerException
在 Optional 中,使用 isPresent() 方法检查 Optional 是否包含值。例如:
Optional opt1 = Optional.ofNullable("a"); System.out.println(opt1.isPresent() ? "有值" : "没有值"); //有值 Optional opt2 = Optional.ofNullable(null); System.out.println(opt2.isPresent() ? "有值" : "没有值"); //没有值
上面的用法和直接使用 if 进行判断没有什么区别。
Optional 类提供了如下方法来获取值:
使用 get() 方法获取值(如果 Optional 为空,则抛出 NoSuchElementException)。
使用 orElse(T other) 方法获取值,如果 Optional 为空,则返回其他值。
使用 orElseGet(Supplier<? extends T> other) 方法获取值,如果 Optional 为空,则调用其他值的 Supplier 函数。
使用 orElseThrow(Supplier<? extends X> exceptionSupplier) 方法获取值,如果 Optional 为空,则抛出由提供的 Supplier 抛出的异常。
示例:
Optional<String> opt1 = Optional.ofNullable("value1"); Optional<String> opt2 = Optional.ofNullable(null); System.out.println(opt1.get()); //System.out.println(opt2.get()); // NoSuchElementException 错误 // 如果 opt2 中没有值,则返回指定的默认值 // 如果有值,则什么也不做 System.out.println(opt1.orElse("default1")); System.out.println(opt2.orElse("default2")); // default2 // 如果 opt2 中没有值,则执行提供的 lambda 表达式,返回 default3 字符串 System.out.println(opt2.orElseGet(() -> "default3")); // default3 // 如果 opt2 中没有值,则抛出 java.lang.IllegalStateException 异常 System.out.println(opt2.orElseThrow(() -> new IllegalStateException()));
Optional 类提供了如下两个方法,用来对值进行转换:
使用 map(Function<? super T, ? extends U> mapper) 方法对值进行转换。如果存在一个值,则对其应用所提供的映射函数(Function),如果结果为非空,则返回一个描述结果的 Optional 。否则返回一个空的 Optional 。
使用 flatMap(Function<? super T, Optional<U>> mapper) 方法进行链式转换。如果存在一个值,则对其应用所提供的带有 Optional 的映射函数,并返回该结果,否则返回一个空的 Optional。该方法类似于 map(Function),但所提供的映射器的结果已经是一个 Optional,如果调用该映射器,flatMap 不会用额外的 Optional 对其进行包装。
注意:如果 Optional 没有值(即 isPresent() 返回 false),则不会调用 map 和 flatMap 方法。
示例:
Optional<String> opt1 = Optional.ofNullable("value1"); // map 方法会返回一个新的 String val1 = opt1.map(v -> { System.out.println("opt1 = " + v); return v.toUpperCase(); }).orElse(""); System.out.println("val1 = " + val1); Optional<String> opt2 = Optional.ofNullable(null); String val2 = opt2.map(v -> { System.out.println("opt2 = " + v); return v.toUpperCase(); }).orElse(""); System.out.println("val2 = "+ val2); //输出: //opt1 = value1 //val1 = VALUE1 //val2 =
flatMap 示例:
Optional<String> opt1 = Optional.ofNullable("value1"); String val1 = opt1.flatMap(v -> { System.out.println("flatMap1 = " + v); return Optional.of(v.toUpperCase()); }).flatMap(v -> { System.out.println("flatMap2 = " + v); return Optional.of("[" + v + "]"); }).orElse(""); System.out.println("val1 = " + val1); //输出: //flatMap1 = value1 //flatMap2 = VALUE1 //val1 = [VALUE1]
Optional 类使用 filter(Predicate<? super T> predicate) 方法根据条件过滤值。如果存在一个值,且该值符合给定的谓词(Predicate),则返回一个描述该值的 Optional ,否则返回一个空的 Optional (即 isPresent 方法为 false)。例如:
Optional<String> opt1 = Optional.ofNullable("value1"); String val1 = opt1.filter(x -> x.equals("value1")).orElse("-"); System.out.println("val1 = " + val1); String val2 = opt1.filter(x -> x.equals("value2")).orElse("-"); System.out.println("val2 = " + val2); //输出: //val1 = value1 //val2 = -
高效使用 Optional 的关键在于,使用一个或者接受正确值、或者返回另一个替代值的方法。
ifPresent 方法的另一种形式可以接受一个函数。如果存在可选值,那么它会将该值传递给函数,否则不会进行任何操作:
optionalValue.ifPresent(v -> Process v); // 类似 if(optional.isPresent()) { //...进行操作... }
如果你希望当值存在时将它添加到一个集合中,可以如下:
final List<String> results = new ArrayList<>(); optionalValue.ifPresent(v -> results.add(v));
或者更简单一点:
optionalValue.ifPresent(results::add);
当调用这种形式的 ifPresent 方法时,不会返回任何值。如果你希望对结果进行处理,可以使用 map 方法:
Optional<Boolean> added = optionalValue.map(results::add);
现在 added 有可能是以下三种值:
(1)被封装到 Optional 中的 true。
(2)被封装到 Optional 中的 false。
(3)一个空的可选值。
注意:这个 map 方法类似于 Stream 接口的 map 方法。你可以简单地将一个可选值想象为一个大小为 0 或 1 的流,返回结果的大小也是 0 或者 1。
你已经了解了当一个可选值存在时应该如何对它优雅地进行处理。另一种使用可选值的方式是,当没有值存在时,产生一个替代值(即默认值)。通常,当没有可匹配项时,你会希望使用一个默认值,例如一个空字符串:
// 如果 Optional 中有值,则返值;否则,返回 orElse 指定的默认值。 Optional<String> opt1 = Optional.ofNullable("value1"); System.out.println(opt1.orElse("-")); // value1 Optional<String> opt2 = Optional.ofNullable(null); System.out.println(opt2.orElse("default value")); // default value
你还可以调用代码来计算默认值:
// 如果 Optional 中有值,则返值;否则,调用 orElseGet 方法指定的函数,使用函数获取默认值 String result = optionalString.orElseGet(() -> System.getProperty("user.dir"));
或者,如果你希望在没有值的时候抛出另一个异常,如下:
// 如果 Optional 中有值,则返值;否则,调用 orElseThrow 方法指定的函数,抛出一个异常 String result = optionalString.orElseThrow(NoSuchElementException::new);
假设你有一个会返回 Optional<T> 的方法 f,并且目标类型 T 有一个会返回 Optional<U> 的方法 g。如果它们都是普通的方法,你可能会考虑通过调用 s.f().g() 将它们组合起来。但是这种组合在这里是行不通的。因为 s.f() 方法返回的是 Optional<T>,而不是T。但是,我们可以调用:
Optional<U> = s.f().flatMap(T::g);
如果 s.f() 存在,那么会继续调用 g。否则,会返回一个空的 Optional<U>。
显然,如果你有更多的返回 Optional 值的方法或表达式,你可以重复这个步骤,将它们都组合起来。然后你只需要通过不断调用 flatMap,就可以创建一个调用的流水线,只有当其中每个部分都成功时,整个流水线才返回成功。