简述
Optional 针对空指处理而设计的类型。
Java 8借鉴了Scala
和Haskell
,提供了一个新的Optional模板,可以用它来封装可能为空的引用。这是一个可以为null
的容器对象。
使用 Optional 的好处是可以以一种专门针对null的处理方式,来避免值可能存在 null 导致出现的程序异常。你可以理解为:处理null,就是你的业务。
在实际使用过程中,你会发现 Optional 的灵活性有时候会让你想用在任何可能出现null的地方,不过凡事都有套路可循,只要清楚利弊就知道该如何选择。
从两个方面说一下 Optional
- 常用API
- 项目使用套路
只讲 API 不讲使用套路的文章都是耍流氓。
API说明
先看一下常用API,后面再讲实际使用场景。
- 构建API: 构建Optional对象:of()、ofNullable()、empty()
- 获取API: 获取Optional对象包装的值:get()、orElse()、orElseGet()、orElseThrow()
- 判断API:对Optional对象里包装的值做一些逻辑判断:isPresent()、ifPresent()、filter()
- 转换API:将Optional对象里包装的值转换成一个新的值:map()、flatMap();
构建API
Optional.of()
作用:构建 Optional 对象,不允许传入的值为
null
,传入就 null 马上抛异常。
这个API要慎用,一般在使用 Optional 时,就是要防住 null,这个API 上来就直接抛异常,一点机会也不给。
代码语言:javascript复制public static void testOf() {
Optional<String> op1 = Optional.of("Hello World");
System.out.println(op1.isPresent()); // 输出 true
System.out.println(op1.get()); // 输出 Hello
Optional<String> op2 = Optional.of(null); // 抛出异常
}
Optional.ofNullable()
常用API。
允许传入的值为 null,如果值为 null,返回一个空的 Optional 传入 null 并不抛异常。
使用 Optional.get() 获取值时,有值正常返回,值为 null 抛异常。
代码语言:javascript复制public static void testOfNullable() {
//传入不报错
Optional<String> name = Optional.ofNullable(null);
System.out.println(name); //直接输出是 Optional.empty
System.out.println(name.isPresent()); //判断是否有值
System.out.println(name.get()); // get() 抛异常
}
Optional.empty()
作用:创建一个空的 Optional 对象,一般很少直接这样写,都是通过 ofNullable 直接接住变量。
empty()
方法创建的对象没有值,如果对 emptyOpt 变量调用isPresent()
方法会返回false,
调用get()方法抛出NullPointerException
异常。
public static void testEmpty() {
Optional<String> emptyOpt = Optional.empty();
System.out.println(emptyOpt.isPresent()); // 输出 false
System.out.println(emptyOpt.get()); // 抛异常
}
获取API
这一组API很常用:get、orElse、orElseGet、orElseThrow。 get() 使用简单,后面三个简单一些业务逻辑。
Optional.get()
作用:获取 Optional 中的数据。
可以看上一个例子。使用 Optional 时,如查值是 null,get 会抛异常。
orElse()
作用:如果有值就返回不执行,否则如果值为null,也会执行orElse();
这种做用是相当于在特定场景下的用法可以用它来代替if..else..
来完成很简洁的逻辑判断。
看到 orElse 中只有一个String不能做别的事?当然不是,可以写一个方法,orElse调用该方法,就可以写其他代码。
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args){
testOrElse(null);
}
public static void testOrElse(String nullValue) {
// 入参为 null,执行 orElse
String optional = Optional.ofNullable(nullValue).orElse("Su");
System.out.println(optional);
// 入参为 Susan,不执行 orElse
String nonNullOptional = Optional.ofNullable("Susan").orElse("Su");
System.out.println(nonNullOptional);
}
}
结果
Su Susan
orElseGet()
作用:入参为null时,才会执行 orElseGet()。
和orElse的区别: 在optional为空值的情况下orElse和orElseGet都会执行,当optional不为空时,orElseGet不会执行。
代码语言:javascript复制import java.util.Optional;
public class OptionalTest {
public static void main(String[] args){
testOrElseGet(null);
}
public static void testOrElseGet(String nullValue) {
String optionalGet = Optional.ofNullable(nullValue).orElseGet(() -> "Xiao");
System.out.println(optionalGet);
String nonNullOptionalGet = Optional.ofNullable("Molly").orElseGet(() -> "Xiao");
System.out.println(nonNullOptionalGet);
}
}
结果
Xiao Molly
orElseThrow()
作有:当参数为空时,抛异常。
代码语言:javascript复制import java.util.Optional;
public class OptionalTest {
public static void main(String[] args){
testOrElseThrow(null);
}
public static void testOrElseThrow(String nullValue) {
try {
Optional.ofNullable(nullValue)
.orElseThrow(()-> new Exception("参数为空"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果
java.lang.Exception: 参数为空 at com.test.OptionalTest.lambdatestOrElseThrow2(OptionalTest.java:30) at java.util.Optional.orElseThrow(Optional.java:290) at com.test.OptionalTest.testOrElseThrow(OptionalTest.java:30) at com.test.OptionalTest.main(OptionalTest.java:9)
判断API
Optional.isPresent()
作用:判断变量是否为null
这个比较常用,用来提前判断一下值是否为空。实际业务场景中,很参数传入的时候,程序是不知道是否为null的,可以使用这个先进行判断。
代码语言:javascript复制public static void testIsPersent() {
//传入不报错
Optional<String> name = Optional.ofNullable(null);
System.out.println(name.isPresent()); //判断是否有值: true; false
if (name.isPresent()) {
System.out.println("有值");
} else {
System.out.println("空值");
}
}
Optional.ifPresent()
很常有的API。
作用:如果值不为null,则可以执行后续操作。
看这段代码,如果值为null不会进入 ifPresent,如果不为null,则进入。这样就不需要if来判断这种特性很方便写一个流畅的工作流风格代码。
代码语言:javascript复制public static void testIfPersent() {
String value = "test";
Optional.ofNullable(value)
.ifPresent((s) -> {
System.out.println(s);
}
}
Optional.filter()
代码语言:javascript复制作用:条件过滤,根据条件过滤不满足条件的数据。
public List<UserInfo> getUsers() {
List<UserInfo> userInfo = userService.getUsers();
List<UserInfo> newUserInfo = Optional
.ofNullable(userInfo)
.filter(u -> u.getPrice() < 1000)
.orElse(UserInfo.builder()
.build());
//直接 return,orElse处理了null,userInfo不会为null
return userInfo;
}
Optional.map()
代码语言:javascript复制作用:映射出新对象。map 时return 什么类型的数据,接收时就必须使用对应的泛型接住。
public static void testMap() {
String val1 = "test";
String val2 = null;
Optional<String> optional = Optional.ofNullable(val2);
Optional<String> newVal = optional.map((a) -> {
System.out.println(a);
return "bb";
});
System.out.println(newVal.get());
}
结果: 入参为 val1 时
bb
入参为 val2 时
Exception in thread "main" java.util.NoSuchElementException: No value present
套路
使用 Optional 时,Optional.get() 如果值为 null,还是会抛异常,那使用 Optional 有什么意义。 Optional 能不能当作返回出参,返回给外部调用。
先说第一个问题,意义在于,Optional 本身不会为 null,不会在被调用时出现空指针而导致异常。由于是通过 Optional 包裹可能出现空值的对象,所以多了一层保护机制。
Optional 不建议做为返回值,至于为什么后面说。
套路1 不返回null
保证返回的数据中绝对不返回null,保证不会因为null引起不可预见的异常。 结合 orElse,来保证如果下面的 list 中查出的数据是null,就返回一个空的ArrayList。 这种写法简单实用。
代码语言:javascript复制public List<User> getUsers() {
List<User> userInfo = userService.getUsers();
return Optional
.ofNullable(userInfo)
.orElse(new ArrayList());
}
套路2 先判断,后使用
业务中从一个Service中获得一个数据,那么先处理一下。
判断List是否为空
代码语言:javascript复制public static void testService() {
List<User> users = userService.getUsers();
Optional<String> usersOptional = Optional.ofNullable(users);
// 这里用 Optional 接住,再进行判断
if (usersOptional.isPresent()) {
//do somthine
}
}
判断List是否为空,不为空执行后续
代码语言:javascript复制public static void testService() {
List<User> users = userService.getUsers();
Optional<String> usersOptional = Optional.ofNullable(users);
usersOptional.ifPresent(user -> {
System.out.println(user.getSize());
})
}
这两种写法,其实很相近,该怎么选择呢,简化一下代码,如果只有在有值的情况下才处理,使用 ifPresent 的处理,是最简洁的。
代码语言:javascript复制public void test() {
//正解
Optional<User> userOpt = Optional.ofNullable(user);
userOpt.ifPresent(System.out.println(user.get()));
//而非
if (userOpt.isPresent()) {
System.out.println(user.get());
}
}
这两种方式都是用在处理没程不需要返回值的情况下。
套路3 结合 Stream 使用
开发中使用Stream应该是用的最多的,还是一样的套路,防止出现 List 为空。
代码语言:javascript复制List<Person> personList = personService.getPersons();
Optional.ofNullable(personList)
.orElseGet(() -> {
System.out.println("personList为null!");
return new ArrayList<>();
})
.stream()
.filter(Objects::nonNull)
.forEach(person -> {
System.out.println(person.getName());
System.out.println(person.getAge());
});
总结
Optional 的功能主要是在保证参数不出现null,通过提供的API来实现,让代码更加健壮。 健壮的代码有助于提高系统的稳定性,是一种不可多得的处理手段。即使不使用 Optional,也需要保证,不直接将 null 返回给上一级调用在方法内处理掉null。 不能相信调用的方法是安全的,需要自己对null有安全的处理。 Optional 只是简化了null的操作,即使没有 Optional 也要对null的处理放在一个重点关注的位置。