Optional类详解

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));
    }
}

该方法通常会结合filterifPresent等使用.

当用户存在时, 获取用户名, 在用户名不为空时输出到控制台:

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很多时候可以被ifPresentifPresentOrElse替换.

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类实践有:

  1. jdk中的streamfindAnyfindFirst
  2. spring data 的repository

Optional类的核心是空安全, 也就是说, 有判空的地方就可能有Optional发挥作用的空间.

推荐使用Optional的场景:

  1. 作为返回值, 可能返回null, 需要调用者进行判断的场景

  2. 嵌套较多的if判断, 可以使用Optional优化

基本上使用Optional的场景就这两种, 应当避免在其他情况下使用Optional

  1. 避免使用Optional作为类或实例的属性
  2. 避免使用Optional作为方法参数
  3. 不要将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));
  }

}

posted @ 2022-08-16 19:02  无以铭川  阅读(2347)  评论(1)    收藏  举报