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.empty
ofNullable()
方法内部有一个三元表达式,如果参数为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最佳指南,优雅解决空指针