JAVA && Spring && SpringBoot2.x — 學(xué)習目錄
Optional是JDK8引入的新特性摄闸,旨在解決空指針異常問題(后文采用NPE代指)。您可以將其看做為一個容器除盏,可以存放null值或非null的對象,Optional類目的是迫使用戶注意可能存在的NPE問題秋柄。關(guān)于它是否可以取代null值,可以參考下Java進階篇(3)—Optional(是否使用Optional來代替null)這篇文章。
Optional不推薦作為屬性撵孤、方法參數(shù)、集合元素
竭望。因為完全可以使用null值來取代Optional邪码,并且Optional效率更加低下。甚至Optional類不能進行序列化咬清。它的使用場景便在于可以作為方法返回值闭专。來使得用戶注意存在的NPE問題。
Optional類更像一把雙刃劍旧烧,它可以盡量避免NPE對程序產(chǎn)生的潛在威脅影钉,而且鏈式操作,可以更加直觀掘剪、優(yōu)雅的對復(fù)雜對象進行處理平委。但是它并不能解決NPE問題,并且它作為一個引用夺谁,效率會更加的低下廉赔。我們在使用過程中愚墓,不能濫用Optional類。
1. Optional類如何構(gòu)建
既然Optional為存放對象的容器昂勉,我們首先考慮的如何將對象存入到Optional容器中。
方法 | 作用 |
---|---|
Optional.of(obj) | 它要求傳入的 obj 不能是 null 值的, 若是null則拋出NullPointerException 異常上了 |
Optional.empty() | 創(chuàng)建一個空的Optional對象 |
Optional.ofNullable(obj) | 智能的創(chuàng)建Optional實例扫腺,即傳入null則構(gòu)建Optional.empty()岗照,非null則構(gòu)建Optional.of(obj) |
2. Option類如何使用
上面我們說到Optional并不能解決NPE問題,我們使用《1. Optional類如何構(gòu)建》方法笆环,將null值存入了Optional類攒至,使用get()
方法獲取值時,會出現(xiàn)NoSuchElementException
異常躁劣。實際上迫吐,他只是將NullPointerException
異常進行了轉(zhuǎn)化。在調(diào)用get()
方法時账忘,必須調(diào)用isPresent()
方法志膀,去判斷對象是否存在。
2.1 Optional使用的錯誤案例
但是你以為這樣就是正確使用Optional的姿勢嗎鳖擒?
反例:若是我們以為這樣可以解決NullPointException問題溉浙,那么可真是有點大材小用了。
@Test
public void test2() {
User user = null;
//構(gòu)建Optional對象
Optional<User> optional = Optional.ofNullable(user);
//判斷Optional對象是否存在
if (optional.isPresent()) {
//業(yè)務(wù)操作
System.out.println(optional.get());
} else {
//異常處理
throw new RuntimeException("user不存在");
}
}
由上例我們可以看出:isPresent()
方法實際上和obj!=null
方法沒有任何的區(qū)別蒋荚,若是沒有isPresent()
方法而單獨使用get()調(diào)用戳稽。也可能存在NoSuchElementException
異常。
Optional類型不能作為字段或者方法參數(shù)期升,Optional是一個類庫方法惊奇,可明確表示可能無值請求下的返回類型。Optional類型不可序列化播赁。
使用Optional
時颂郎,應(yīng)該盡量不調(diào)用Optional.get()
方法,Optional.isPresent()
更應(yīng)該被看做一個私有的方法(在Optional中其他方法借助該方法進行邏輯判斷)行拢,不應(yīng)該用其處理NullPointerException
異常祖秒。
2.2 Optional正確的使用方式
在Optional中我們真正可以依賴的是除了isPresent()和get()方法的其他方法:我們可以通過下列的方法完成Optional鏈式操作。
在鏈式操作中舟奠,若出現(xiàn)NPE問題竭缝,或Filter返回false,那么便會執(zhí)行我們的異常方案沼瘫,即
orElse()/ofNullable /orElseThrow
方法抬纸,我們無需在為空指針異常而擔憂。我們便可以對該對象進行任意的操作耿戚。
//流類型轉(zhuǎn)化
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
//if...else
public T orElse(T other)
public T orElseGet(Supplier<? extends T> other)
//若存在的處理邏輯
public void ifPresent(Consumer<? super T> consumer)
//篩選
public Optional<T> filter(Predicate<? super T> predicate)
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
//若元素為null湿故,則拋出指定的異常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
1. orElse 存在即返回阿趁,無則返回默認值
return user.orElse(null); //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);
2. orElseGet 存在即返回,無則由函數(shù)產(chǎn)生
@Test
public void test() {
User user = new User().setUserName("ll");
Optional<User> optional = Optional.ofNullable(user);
//相當于三目表達式:return user.isPresent() ? user: getUser("aa");
User user1 = optional.orElseGet(() -> getUser("aa"));
System.out.println(user);
}
private User getUser(String id) {
return new User();
}
3. orElseThrow 存在即返回坛猪,無則拋出異常
@Test
public void test5() {
User user = null;
Optional<User> optional = Optional.ofNullable(user);
User user1 = optional.orElseThrow(() -> new RuntimeException("user不能為null"));
}
4. ifPresent 若對象不為null脖阵,則進行操作
@Test
public void test3() {
User user = new User().setUserName("ll");
Optional<User> optional = Optional.ofNullable(user);
optional.ifPresent(o -> {
Integer id = o.getId();
});
//而不要下邊那樣。
if (optional.isPresent()) {
System.out.println(optional.get());
}
}
5. map 若對象存在墅茉,則進行轉(zhuǎn)換
@Test
public void test4() {
User user = null;
Optional<User> optional = Optional.ofNullable(user);
//map返回類型依舊為Optional類型
Optional<Integer> integer = optional.map(u -> u.getId());
//若使用orElse()命黔,只要出現(xiàn)null值,那么便轉(zhuǎn)換為1
Integer integer1 = optional.map(u -> u.getId()).orElse(1);
System.out.println(integer1);
}
//相當于下面的使用
@Test
public void test41() {
User user = null;
if (user != null && user.getId() != null) {
System.out.println(user.getId());
} else {
System.out.println(1);
}
}
6. filter過濾條件就斤,不符合條件悍募,則返回null
@Test
public void test6() {
User user = new User().setUserName("aa").setUserAge(20);
Optional<User> user1 = Optional.of(user);
//中間操作,不符合條件則為null
User user2 = user1.filter(u -> "ab".equals(u.getUserName())).orElseThrow(RuntimeException::new);
}
2.3 實戰(zhàn)
校驗user.getAge()是否0<age<=100洋机,但是要判斷是否存在NPE坠宴。
/**
* 校驗user.getAge()是否0<age<=100,但是要判斷是否存在NPE绷旗。
*/
private static void testMapElse() {
User user = new User();
Boolean flag = Optional.ofNullable(user)
.map(u -> u.getAge())
.map(u -> (u > 0 && u <= 100))
.orElse(false);
System.out.println(flag);
}
2.4 封裝工具類
public class OptionalUtils {
/**
* 若是null喜鼓,則傳入默認值
*/
public static <T> T nullToDefault(T value, T defaultValue) {
return Optional.ofNullable(value).orElse(defaultValue);
}
/**
* 校驗參數(shù)的時候,首先得去判斷是否為空衔肢,太麻煩了颠通,故抽取出來。先判斷若是參數(shù)為null膀懈,則直接返回false顿锰。
* 否則的話,則去執(zhí)行filter的過濾操作
*/
public static <T> boolean checkNoNPE(T value, Function<T, Boolean> filter) {
return Optional.ofNullable(value).map(filter).orElse(false);
}
/**
* 校驗參數(shù)是否在指定范圍之內(nèi)启搂。
*/
public static boolean between(Double target, double begin, double end) {
return Optional.ofNullable(target)
.map(t -> (t >= begin && t < end))
.orElse(false);
}
/**
* 三目表達式——supplier?a:b
*/
public static <T> T conditionalAB(Supplier<Boolean> supplier, T a, T b) {
return nullToDefault(supplier.get(), false) ? a : b;
}
/**
* 取代三目表達式
*/
public static <T> T conditionalAB(boolean result, T a, T b) {
return result ? a : b;
}
}
3. lamda與Optional搭配(Guava提供)
使用jdk的lamda表達式解析集合時硼控,在最后時可以轉(zhuǎn)化為Optional。
MoreCollectors的API使用:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
public static void main(String[] args) {
//集合
List<User> users = new ArrayList<>();
User u1 = new User();
u1.setAge(110);
User u2 = new User();
u2.setAge(10);
users.add(u1);
users.add(u2);
//必須有l(wèi)imit(1)胳赌,將元素設(shè)置為Optional牢撼。在使用之前的方法
Optional<User> collect = users.stream().limit(1).collect(MoreCollectors.toOptional());
System.out.println(collect.orElse(null));
}
find的相關(guān)API:
public static void main(String[] args) {
List<User> users = new ArrayList<>();
User u1 = new User();
u1.setAge(110);
User u2 = new User();
u2.setAge(10);
users.add(u1);
users.add(u2);
/**
* findAny返回值
*/
Optional<String> s = users.stream().findAny().map(User::getGj);
/**
* findFirst返回值
*/
Optional<String> s1 = users.stream().findFirst().map(User::getGj);
}