Optional类详解
- Optional类详解
- 一. 介绍
- 二. 创建Optional的方式
- 三. 方法解析
- 1. isPresent()
- 2. isEmpty()
- 3. get()
- 4. ifPresent(Consumer<? super T>)
- 5. ifPresentOrElse(Consumer<? super T>, Runnable)
- 6. filter(Predicate<? super T>)
- 7. map(Function<? super T, ? extends U>)
- 8. orElse(T)
- 9. flatMap(Function<? super T, ? extends Optional<? extends U>>)
- 10. or(Supplier<? extends Optional<? extends T>>)
- 11. stream()
- 12. orElseGet(Supplier<? extends T>)
- 13. orElseThrow(Supplier<? extends X>)
- 14. orElseThrow()
- 四. 实践
- 五. 用例
Optional类详解
一. 介绍
Optional类是java8版本引入的一个用于处理空安全操作的类型.
Optional是一个容器, 内部持有一个值, 该值可以为空, 同时Optional是final的, 你不能修改Optional对象的value的引用.
以下是源码类注释的翻译:
一个容器对象,可能包含也可能不包含非null值。如果存在值,则isPresent()返回true 。如果不存在任何值,则该对象被认为是空的并且isPresent()返回false 。
提供了依赖于包含值是否存在的其他方法,例如orElse() (如果不存在值,则返回默认值)和ifPresent() (如果存在值,则执行操作)。
这是一个基于值的类;在Optional的实例上使用身份敏感操作(包括引用相等 ( == )、身份哈希码或同步)可能会产生不可预知的结果,应该避免.Optional主要用作方法返回类型,其中明确需要表示“无结果”,并且使用null可能会导致错误。类型为Optional的变量本身永远不应为null ;它应该始终指向一个Optional实例。
Optional类是一个相当简单的类, 即便是在java17, 源码中代码行数也不超过500行(带注释).
只需要阅读源码理解方法, 就可以迅速掌握Optional的使用.
二. 创建Optional的方式
1. Optional.of(T)
通过一个不为空的值构造一个Optional对象.
public static <T> Optional<T> of(T value) {
return new Optional<>(Objects.requireNonNull(value));
}
2. Optional.ofNullable(T)
通过一个可以为空的值构造一个Optional对象.
内部值为null的Optional实例只有一个: Optional内的Optional.EMPTY对象.
public static <T> Optional<T> ofNullable(T value) {
return value == null ? (Optional<T>) EMPTY : new Optional<>(value);
}
3. Optional.empty()
返回值为null的Optional对象
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
三. 方法解析
1. isPresent()
判断Optional对象持有的值是否为null, 不为null返回true, 否则返回false
public boolean isPresent() {
return value != null;
}
2. isEmpty()
与isPresent()相反, 值为null时才返回true.
java11中加入.
3. get()
获取内部持有的值, 一般不会直接调用, 该方法在内部值为null时会抛出NoSuchElementException异常.
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
4. ifPresent(Consumer<? super T>)
在值存在的情况下, 以值为参数, 调用Consumer.
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
例如: 当值存在的时候输出到控制台
Optional<String> op = ....;
op.ifPresent(System.out::println);
5. ifPresentOrElse(Consumer<? super T>, Runnable)
在值存在的情况下, 以值为参数, 调用Consumer, 否则调用Runnable.
java9中新增.
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
6. filter(Predicate<? super T>)
值不存在则直接返回(所有的空值Optional对象都是同一个), 如果存在, 则以值为参数调用Predicate, 如果返回true, 返回自己, 否则返回空值对象.
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}
7. map(Function<? super T, ? extends U>)
在内部值不为null的情况下, 通过传入的Function, 将内部值处理成新的值, 并构造成新的Optional对象返回.
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
return Optional.ofNullable(mapper.apply(value));
}
}
该方法通常会结合filter或ifPresent等使用.
当用户存在时, 获取用户名, 在用户名不为空时输出到控制台:
Optional<User> userOption = ...;
userOption.map(User::getUsername)
.filter(Predicate.not(String::isEmpty))
.ifPresent(System.out::println);
8. orElse(T)
获取内部持有的值, 如果值为null, 则返回该方法的参数值.
public T orElse(T other) {
return value != null ? value : other;
}
该方法一般是用来提供一个默认值.
并不推荐orElse(null)的使用方式, 因为本身Optional即是用来针对空安全做处理的, 如果orElse(null), 又可能要进行一次null检查, 这并不符合Optional的使用逻辑.
一般来说, 如果只需要在非空的情况下执行的逻辑, 使用ifPresent, 而需要额外进行某些检查的时候可以使用isPresent, 但isPresent并不是首选方案, 注意isPresent很多时候可以被ifPresent或ifPresentOrElse替换.
9. flatMap(Function<? super T, ? extends Optional<? extends U>>)
将其内部值转换成另一个Optional对象并返回.
这个方法与map类似, 但区别在于, 传入的函数的返回值是Optional
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r);
}
}
这个方法在链式调用返回Optional方法的代码中比较有用.
10. or(Supplier<? extends Optional<? extends T>>)
java9可用.
如果内部值为空, 在使用supplier获取另一个Optional对象并返回.
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
Objects.requireNonNull(supplier);
if (isPresent()) {
return this;
} else {
@SuppressWarnings("unchecked")
Optional<T> r = (Optional<T>) supplier.get();
return Objects.requireNonNull(r);
}
}
11. stream()
java9可用
内部值不为null时转为单个元素的stream, 一般用于内部元素是个集合时转换成stream处理.
public Stream<T> stream() {
if (!isPresent()) {
return Stream.empty();
} else {
return Stream.of(value);
}
}
12. orElseGet(Supplier<? extends T>)
如果内部值为空, 就返回supplier提供的值. 相当于orElse的懒加载版本.
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
13. orElseThrow(Supplier<? extends X>)
如果为空就抛出supplier提供的异常.
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
14. orElseThrow()
java10可用.
如果为空就抛出NoSuchElementException
public T orElseThrow() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
四. 实践
目前常用并且比较好的Optional类实践有:
- jdk中的
stream的findAny和findFirst - spring data 的
repository
Optional类的核心是空安全, 也就是说, 有判空的地方就可能有Optional发挥作用的空间.
推荐使用Optional的场景:
-
作为返回值, 可能返回null, 需要调用者进行判断的场景
-
嵌套较多的if判断, 可以使用
Optional优化
基本上使用Optional的场景就这两种, 应当避免在其他情况下使用Optional
- 避免使用
Optional作为类或实例的属性 - 避免使用
Optional作为方法参数 - 不要将null赋值给
Optional类型的变量,Optional.empty()就是空的Optional对象.
1. 同一个对象的多层判空
例如如下的代码:
String cityName = user.getAddress().getCountry().getCity().getName();
// ...
为了避免NPE, 需要进行判空:
if(user != null) {
Address addr = user.getAddress();
if(addr != null) {
Country country = addr.getCountry();
if(country != null) {
City city = country.getCity();
if(city != null) {
String cityName = city.getName();
// ....
}
}
}
}
转换成Optional
Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCountry)
.map(Country::getCity)
.map(City::getName)
.ifPresent(cityName -> {
// ...
})
2. 默认值
像下面这样, 如果为空指定一个默认值的情况:
String cityName = null;
if(user != null) {
Address addr = user.getAddress();
if(addr != null) {
Country country = addr.getCountry();
if(country != null) {
City city = country.getCity();
if(city != null) {
cityName = city.getName();
}
}
}
}
if(cityName == null) {
cityName = "北京";
}
// ...
String cityName = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCountry)
.map(Country::getCity)
.map(City::getName)
.orElse("北京");
// ...
有时候默认值不会是这样一个字面量, 可能是来自于某个函数:
public String getDefaultCity() {
//
}
可以使用orElseGet避免重复运算:
String cityName = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCountry)
.map(Country::getCity)
.map(City::getName)
.orElseGet(this::getDefaultCity);
3. 条件判断
有时不仅会进行判空, 还会做一些额外的判断, 例如:
if(user != null) {
String username = user.getUsername();
if(username != null && StringUtils.isNotEmpty(username)) {
// ...
}
}
如果用Optional:
Optional.ofNullable(user)
.map(User::getUsername)
.filter(StringUtils::isNotEmpty)
.ifPresent(username -> {
// ....
})
4. 抛出异常
有时候可以直接通过orElseThrow直接抛出异常
User user = userRepository.findById(userId).orElseThrow(UserNotExistsException::new);
如果不指定要抛出的异常, 则orElseThrow抛出的异常是: NoSuchElementException
可以通过异常捕获统一处理:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<?> noSuchElementExceptionHandler() {
return ResponseEntity.notFound().build();
}
}
5. 数据转集合
通常是在通过map转换获取到一个集合对象, 然后调用stream之后转流处理
@Getter
class User {
private List<Role> roles;
// ....
}
@Getter
class Role {
private String roleName;
// ....
}
从数据库中查询id为1的用户, 获取该用户的所有角色名
List<String> roleNames = userRepository.findById(1L)
.map(User::getRoles)
.stream()
.flatMap(Collection::stream)
.map(Role::getRoleName)
.collect(Collectors.toList());
另一种用法是将Stream<Optional<T>>内的Optional解包装成Stream<T>
Stream<Optional<T>> os = ..
Stream<T> s = os.flatMap(Optional::stream)
五. 用例
仅做示例: 一个简单的HttpServletRequest工具
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class HttpServletUtil {
private static final String[] REMOTE_ADDR_HEADER_NAMES =
{"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"};
public static Optional<HttpServletRequest> getHttpServletRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
public static Optional<HttpServletResponse> getHttpServletResponse() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getResponse);
}
public static Optional<String> getRequestURI() {
return getHttpServletRequest().map(HttpServletRequest::getRequestURI);
}
public static Optional<String> getRemoteAddrFromHeader() {
var requestOptional = getHttpServletRequest();
if (requestOptional.isEmpty()) {
return Optional.empty();
}
var request = requestOptional.get();
return Stream.of(REMOTE_ADDR_HEADER_NAMES)
.map(request::getHeader)
.filter(Predicate.not(String::isBlank))
.findFirst();
}
public static Optional<String> getRemoteAddr() {
return getRemoteAddrFromHeader().or(() -> getHttpServletRequest().map(HttpServletRequest::getRemoteAddr));
}
public static Optional<String> getHeader(String headerName) {
return getHttpServletRequest().map(request -> request.getHeader(headerName));
}
}

浙公网安备 33010602011771号