Java Optional使用的最佳實踐
作者丨安琪拉
來源丨安琪拉的博客
我們經常在編程的遇到需要做空判斷的場景。
例如拉哥最近遇到的一個場景,需要獲取任務節(jié)點的執(zhí)行完成時間,是Date類型的,但是上游需要時間的毫秒,所以寫了這么一段代碼
public Result<TaskInfoVo> getTaskInfo(String taskId){
TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
//返回任務視圖
TaskInfoVo taskInfoVo = TaskInfoVo
.builder()
.taskName(taskNode.getName())
.finishTime(taskNode.getFinishTime().getTime())
.user(taskNode.getUser())
.memo(taskNode.getMemo())
.build()));;
return Result.ok(taskInfoVo);
}
class TaskInfoVo {
/**
** 完成時間
**/
Long finishTime;
}
但是運行時發(fā)現任務的執(zhí)行時間可能為null,taskNode.getFinishTime().getTime() 這里會產生NPE(空指針異常)。
如果不使用 Optional,一般判空的方式可以這么寫:
//第一種判空
if (Objects.notNull(taskNode.getFinishTime())) {
taskInfoVo.set(taskNode.getFinishTime().getTime());
}
//第二種判空 保留builder模式
TaskInfoVo
.builder()
.finishTime(taskNode.getFinishTime() == null ? null : taskNode.getFinishTime().getTime())
.build()));
第一種方式就不能使用builder模式,值的設置割裂開了。
第二種方式采用三目表達式也還算清晰,只是執(zhí)行了二次 getFinishTime(),如果在不使用Optional的二種方式,更推薦第二種,清晰一致。
再說第三種,使用Optional 方式。如下所示:
public Result<TaskInfoVo> getTaskInfo(String taskId){
TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
//返回任務視圖
TaskInfoVo taskInfoVo = TaskInfoVo
.builder()
.taskName(taskNode.getName())
.finishTime(Optional.ofNullable(taskNode.getFinishTime()).map(date -> date.getTime()).orElse(null))
.user(taskNode.getUser())
.memo(taskNode.getMemo())
.build()));;
return Result.ok(taskInfoVo);
}
首先,我們使用 Optional.ofNullable 把可能為空的值包了一層,然后用map和orElse 來設置存在值和為空分別的結果。
我們來看看Optional 內部的實現細節(jié),代碼很清晰,也很簡單。
/**
**@since 1.8 jdk1.8引入
**/
public final class Optional<T> { // final 修飾,不能被繼承,也就是不允許重寫
/**
* Common instance for {@code empty()}. 空對象,但注意,不是null
*/
private static final Optional<?> EMPTY = new Optional<>();
/**
* 存儲的值
*/
private final T value;
private Optional() {
this.value = null;
}
//空數據
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//帶參構造函數
private Optional(T value) {
//value 不能為空
this.value = Objects.requireNonNull(value);
}
//一般不建議使用of,因為傳入的值不允許為空,否則拋異常
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
//一般確定value不為null,才調用get
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
//是否存在 present是個好詞,源碼經常用
public boolean isPresent() {
return value != null;
}
//如果存在,調用consumer 消費value值
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
//判斷,predicate有test函數,filter返回過濾后Optional
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
//映射轉化 mapper
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));
}
}
//和map相比,flatMap返回結果不能為空,否則拋NPE
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
//值不為空 返回 否則返回其他
public T orElse(T other) {
return value != null ? value : other;
}
//值不為空,返回,否則返回傳入的其他值
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
//值不為空,直接返回,否則丟出提供的異常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
}
//列出了 Objects
class Objects {
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
}
我們來分別看一下這些方法的使用場景
-
希望為空時獲得默認值
Task defalutTask = new Task("defalutTask", defalutTaskInfo);
return Optional.ofNullable(task).orElse(defalutTask); -
希望為空時進行函數求值
Task defalutTask = new Task("defalutTask", defalutTaskInfo);
return Optional.ofNullable(task).orElseGet(() -> assembleDefaultTask());
private Task assembleDefaultTask() {
Task currentTask = ExecutorManager.getCurrentTaskFromContext();
currentTask.reset();
return currentTask;
}orElseGet 與 orElse 的區(qū)別在于 orElseGet 的參數是個 Supplier 對象,orElse 是值。
Supplier 是java8 引入的。Supplier 接口僅包含一個無參的方法: T get() 。用來獲取一個泛型參數指定類型的對象數據。
.orElseGet(() -> assembleDefaultTask());
-
空判斷
if (taskOptional.isPresent()) {
//doSomeThing();
} -
如果存在,執(zhí)行下一步消費者動作。
taskOptional.ifPresent([Consumer]<? super [T]> consumer)
//還是以日期處理為例
Optional.ofNullable(previewStep.getFinishTime()).ifPresent(date -> processPreviewVO.setFinishTime(date.getTime()));消費者大家不陌生,就是執(zhí)行一套消費動作,lamada 的寫法簡化了代碼,但是降低了可讀性,
date -> processPreviewVO.setFinishTime(date.getTime())實際上就是這段代碼:new Consumer<Date>() {
@Override
public void accept(Date date) {
processPreviewVO.setFinishDate(date.getTime());
}
} -
orElse() 和 orElseGet() 的不同之處
User user = null;
logger.debug("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
logger.debug("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
private User createNewUser() {
logger.debug("Creating New User");
return new User("[email protected]", "1234");
}輸出:
Using orElse
Creating New User
Using orElseGet
Creating New User如果user 為null 時結果是一致的;
但是user 不為null 時,行為是不同的,我們來看下
User user = new User("[email protected]", "1234");
logger.info("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
logger.info("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());輸出:
Using orElse
Creating New User
Using orElseGet為什么呢?
因為orElse ( T value) 函數入參是普通變量,因為會將函數計算好的結果作為參數傳進去。
但是orElseGet(Supplier<? extends T> other)入參實際上是個 Supplier 對象, 因為只有一個方法,所以可以用lambda寫法。
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}對象的函數沒有被調用是不會自己主動執(zhí)行的。
-
值轉化
我們先來看map的例子,實際上面已經演示過了,下面再講解一下。
User user = new User("[email protected]", "niubi");
String email = Optional.ofNullable(user)
.map(u -> u.getEmail()).orElse("[email protected]");map可以對結果進行轉化,返回的是依然是Optional,方便你后續(xù)的鏈式調用。
-
值過濾
User user = new User("[email protected]", "666");
Optional<User> result = Optional.ofNullable(user)
.filter(u -> u.getEmail() != null && u.getEmail().contains("@"));這個filter 方法實際上類似斷言,我總覺得對集合元素進行遍歷,判斷是否符合預期才更適合叫做 filter ,這個功能用的比較少。
-
Optional類的鏈式方法
比如現在我們有這個一個場景,智能游戲機器人需要讓安琪拉釋放火球技能,它需要判斷自己英雄池是否有安琪拉,并且安琪拉火球技能是否ready。
代碼實現:
String result = Optional.ofNullable(heroPool)
.flatMap(heroPool -> heroPool.getHero("angela"))
.flatMap((Angela)hero -> hero.getFireBall())
.map(c -> c.fire())
.orElse("failure");如果用常規(guī)代碼,需要做很多層判空處理。
-
注意事項
-
不要將null賦給Optional 雖然Optional支持null值,但是不要顯示的把null 傳遞給Optional
-
盡量避免使用Optional.get()
-
當結果不確定是否為null時,且需要對結果做下一步處理,使用Optional;
-
在類、集合中盡量不要使用Optional 作為基本元素;
-
盡量不要在方法參數中傳遞Optional;
-
大膽使用map、filter 重構代碼
//1. map 示例
if ( hero != null){
return "hero : " + hero.getName() + " is fire...";
} else {
return "angela";
}
//重構成
String heroName = hero
.map(this::printHeroName)
.orElseGet(this::getDefaultName);
public void printHeroName(Hero dog){
return "hero : " + hero.getName() + " is fire...";
}
public void getDefaultName(){
return "angela";
}
//2. filter示例
Hero hero = fetchHero();
if(hero != null && hero.hasBlueBuff()){
hero.fire();
}
//重構成
Optional<Hero> optionalHero = fetchHero();
optionalHero
.filter(Hero::hasBlueBuff)
.ifPresent(this::fire); -
不要使用 Optional 作為Java Bean Setter方法的參數
因為Optional 是不可序列化的,而且降低了可讀性。
-
不要使用Optional作為Java Bean實例域的類型
原因同上。
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網盤了,歡迎下載!

面試題】即可獲取
