Java 8 Optional 详解
在 Java 8 中,引入了一个新的类——Optional,它提供了一种用于表示可选值而非空引用的类级别解决方案。作为一名 Java 程序员,我们经常会遇到 NullPointerException(NPE),尽管我们与它“熟得就像一位老朋友”,但每次遇到它,程序都会因为使用了一个值为 null 的对象而抛出异常。为了应对这种情况,我们通常会进行大量的 null 值检查,尽管有时候这种检查完全没有必要。Java 8 引入了 Optional 类,以便我们编写的代码不再那么刻薄呆板。
1 没有 Optional 会有什么问题
假设我们有一个实际的应用场景:小王第一天上班,领导老马让他从数据库中根据会员 ID 拉取一个会员的姓名,然后将姓名打印到控制台。小王很快写下了这段代码:
public class WithoutOptionalDemo {class Member {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}public static void main(String[] args) {Member mem = getMemberByIdFromDB();if (mem != null) {System.out.println(mem.getName());}}public static Member getMemberByIdFromDB() {// 当前 ID 的会员不存在return null;}
}
在这个例子中,getMemberByIdFromDB() 方法返回了 null,表示没有获取到该会员。因此,在打印会员姓名时,必须先对 mem 进行判空,否则会抛出 NullPointerException。
2 Optional 是如何解决这个问题的
小王提交代码后,老马建议他尝试使用 Optional 来避免不必要的 null 值检查。下面是使用 Optional 改进后的代码:
import java.util.Optional;public class OptionalDemo {public static void main(String[] args) {Optional<Member> optional = getMemberByIdFromDB();optional.ifPresent(mem -> {System.out.println("会员姓名是:" + mem.getName());});}public static Optional<Member> getMemberByIdFromDB() {boolean hasName = true;if (hasName) {return Optional.of(new Member("沉默王二"));}return Optional.empty();}static class Member {private String name;public Member(String name) {this.name = name;}public String getName() {return name;}// getter / setter}
}
在这个例子中,getMemberByIdFromDB() 方法返回了 Optional<Member>,表示 Member 可能存在也可能不存在。通过 ifPresent() 方法,我们可以直接使用 Lambda 表达式来打印结果,而不需要进行显式的 null 检查。
3 创建 Optional 对象
创建 Optional 对象有三种方式:
-
使用
empty()方法创建一个空的Optional对象:Optional<String> empty = Optional.empty(); System.out.println(empty); // 输出:Optional.empty -
使用
of()方法创建一个非空的Optional对象:Optional<String> opt = Optional.of("沉默王二"); System.out.println(opt); // 输出:Optional[沉默王二]注意,传递给
of()方法的参数不能为null,否则会抛出NullPointerException。 -
使用
ofNullable()方法创建一个即可空又可非空的Optional对象:String name = null; Optional<String> optOrNull = Optional.ofNullable(name); System.out.println(optOrNull); // 输出:Optional.emptyofNullable()方法内部有一个三元表达式,如果参数为null,则返回私有常量EMPTY;否则使用new关键字创建一个新的Optional对象。
4 判断值是否存在
可以通过 isPresent() 方法判断一个 Optional 对象是否存在值:
Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt.isPresent()); // 输出:trueOptional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isPresent()); // 输出:false
在 Java 11 中,还可以使用 isEmpty() 方法判断与 isPresent() 相反的结果。
Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt.isEmpty()); // 输出:falseOptional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isEmpty()); // 输出:true
5 非空表达式
Optional 类提供了一个非常现代化的方法——ifPresent(),允许我们使用函数式编程的方式执行一些代码:
Optional<String> opt = Optional.of("沉默王二");
opt.ifPresent(str -> System.out.println(str.length()));
在 Java 9 中,还可以使用 ifPresentOrElse(action, emptyAction) 方法,在值存在时执行 action,在值不存在时执行 emptyAction。
Optional<String> opt = Optional.of("沉默王二");
opt.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("为空"));
6 设置(获取)默认值
Optional 提供了 orElse() 和 orElseGet() 方法来设置或获取默认值:
-
orElse()方法用于返回包裹在Optional对象中的值,如果该值不为null,则返回;否则返回默认值:String nullName = null; String name = Optional.ofNullable(nullName).orElse("沉默王二"); System.out.println(name); // 输出:沉默王二 -
orElseGet()方法与orElse()方法类似,但参数类型不同。如果Optional对象中的值为null,则执行参数中的函数:String nullName = null; String name = Optional.ofNullable(nullName).orElseGet(() -> "沉默王二"); System.out.println(name); // 输出:沉默王二
7 获取值
get() 方法用于获取 Optional 对象的值,但如果值为 null,该方法会抛出 NoSuchElementException 异常:
public class GetOptionalDemo {public static void main(String[] args) {String name = null;Optional<String> optOrNull = Optional.ofNullable(name);System.out.println(optOrNull.get()); // 抛出 NoSuchElementException}
}
建议使用 orElseGet() 方法来获取 Optional 对象的值。
8 过滤值
Optional 提供了 filter() 方法,用于根据条件过滤值:
public class FilterOptionalDemo {public static void main(String[] args) {String password = "12345";Optional<String> opt = Optional.ofNullable(password);System.out.println(opt.filter(pwd -> pwd.length() > 6).isPresent()); // 输出:false}
}
在这个例子中,filter() 方法的参数是一个 Predicate,用于判断密码的长度是否大于 6。如果条件不满足,则返回一个空的 Optional 对象。
在上例中,由于 password 的长度为 5 ,所以程序输出的结果为 false。假设密码的长度要求在 6 到 10 位之间,那么还可以再追加一个条件。
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);
这次程序输出的结果为 true,因为密码变成了 7 位,在 6 到 10 位之间。
9 转换值
Optional 提供了 map() 方法,用于按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象:
public class OptionalMapDemo {public static void main(String[] args) {String name = "沉默王二";Optional<String> nameOptional = Optional.of(name);Optional<Integer> intOpt = nameOptional.map(String::length);System.out.println(intOpt.orElse(0)); // 输出:4}
}
在这个例子中,map() 方法将字符串类型的 Optional 对象转换为整数类型的 Optional 对象,表示字符串的长度。
public class OptionalMapFilterDemo {public static void main(String[] args) {String password = "password";Optional<String> opt = Optional.ofNullable(password);Predicate<String> len6 = pwd -> pwd.length() > 6;Predicate<String> len10 = pwd -> pwd.length() < 10;Predicate<String> eq = pwd -> pwd.equals("password");boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();System.out.println(result);}
}
上例中把 map() 方法与 filter() 方法结合起来用,前者用于将密码转化为小写,后者用于判断长度以及是否是“password”。
10 总结
Optional 类是 Java 8 引入的一个重要特性,它提供了一种优雅的方式来处理可能为 null 的值,避免了大量的 null 值检查,从而提高了代码的可读性和安全性。通过 Optional,我们可以更清晰地表达程序的意图,减少 NullPointerException 的发生。掌握 Optional 的使用,对于现代 Java 开发来说是非常重要的一项技能。
11 思维导图

12 参考链接
Java 8 Optional最佳指南,优雅解决空指针
