1. 前言
相信不少小夥伴已經被java的NPE(Null Pointer Exception)所謂的空指針異常搞的頭昏腦漲, 有大佬說過“防止 NPE,是程式員的基本修養。”但是修養歸修養,也是我們程式員最頭疼的問題之一。之前,Google Guava項目曾提出用Optional類來包裝對象進而解決NullPointerException。受此影響,JDK8的類中也引入了Optional類,在新版的SpringData Jpa和Spring Redis Data中都已實作了對該方法的支援。我們今天就要盡可能的利用Java8的新特性 Optional來盡量簡化代碼同時高效處理NPE(Null Pointer Exception 空指針異常)
2. 初識Optional
首先我們看下面一段代碼,肯定會很熟悉
public static String getFirstName(User user)
{
if(null == user)
{
return "Unkown";
}
return student.getFirstName();
}
從上面看出,就是對這個執行個體是不是空做了判斷,然後我們看下使用java8 的Optional類之後的代碼
public static String getFirstName(User user)
{
return Optional.ofNullable(user).map(u -> u.getFirstName()).orElse("Unkown");
}
簡單來說,Opitonal類就是Java提供的為了解決大家平時判斷對象是否為空用 會用 null!=obj 這樣的方式存在的判斷,進而令人頭疼導緻NPE(Null Pointer Exception 空指針異常),同時Optional的存在可以讓代碼更加簡單,可讀性跟高,代碼寫起來更高效.
下面,我們就高效的學習一下神奇的Optional類!
3. Optional對象建立
首先我們先打開Optional的内部,去一探究竟 先把幾個建立Optional對象的方法提取出來
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
//我們可以看到兩個構造方格都是private 私有的
//說明 我們沒辦法在外面去new出來Optional對象
private Optional() {
this.value = null;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
//這個靜态方法大緻 是建立出一個包裝值為空的一個對象因為沒有任何參數指派
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//這個靜态方法大緻 是建立出一個包裝值非空的一個對象 因為做了指派
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
//這個靜态方法大緻是 如果參數value為空,則建立空對象,如果不為空,則建立有參對象
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
}
複制代碼
再做一個簡單的執行個體展示 與上面對應
// 1、建立一個包裝對象值為空的Optional對象
Optional<String> optEmpty = Optional.empty();
// 2、建立包裝對象值非空的Optional對象
Optional<String> optOf = Optional.of("optional");
// 3、建立包裝對象值允許為空也可以不為空的Optional對象
Optional<String> optOfNullable1 = Optional.ofNullable(null);
Optional<String> optOfNullable2 = Optional.ofNullable("optional");
複制代碼
我們關于建立Optional對象的内部方法大緻分析完畢 接下來也正式的進入Optional的學習與使用中
4. Optional類的使用
序号 | 方法 | 方法說明 |
1 | | 無參構造,構造一個空Optional |
2 | | 根據傳入的非空value建構Optional |
3 | | 傳回一個空的Optional,該執行個體的value為空 |
4 | | 根據傳入的非空value建構Optional,與Optional(T value)方法作用相同 |
5 | | 與of(T value)方法不同的是,ofNullable(T value)允許你傳入一個空的value, 當傳入的是空值時其建立一個空Optional,當傳入的value非空時,與of()作用相同 |
6 | | 傳回Optional的值,如果容器為空,則抛出NoSuchElementException異常 |
7 | | 判斷當家Optional是否已設定了值 |
8 | | 判斷當家Optional是否已設定了值,如果有值,則調用Consumer函數式接口進行處理 |
9 | | 如果設定了值,且滿足Predicate的判斷條件,則傳回該Optional,否則傳回一個空的Optional |
10 | | 如果Optional設定了value,則調用Function對值進行處理,并傳回包含處理後值的Optional,否則傳回空Optional |
11 | | 與map()方法類型,不同的是它的mapper結果已經是一個Optional,不需要再對結果進行包裝 |
12 | | 如果Optional值不為空,則傳回該值,否則傳回other |
13 | | 如果Optional值不為空,則傳回該值,否則根據other另外生成一個 |
14 | | 如果Optional值不為空,則傳回該值,否則通過supplier抛出一個異常 |
4.1 Optional.get()方法(傳回對象的值)
get()方法是傳回一個option的執行個體值 ,如果value不為空則做傳回,如果為空則抛出異常 "No value present" ,源碼:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
執行個體:
User user=new User();
user.setFirstName("Tom");
Optional.ofNullable(user).get();
4.2 Optional.isPresent()方法(判讀是否為空)
isPresent()方法就是會傳回一個boolean類型值,如果對象不為空則為真,如果為空則false ,源碼:
public boolean isPresent() {
return value != null;
}
執行個體:
User user=new User();
person.setFirstName("Tom");
if (Optional.ofNullable(user).isPresent()){
//寫不為空的邏輯
System.out.println("不為空");
}else{
//寫為空的邏輯
System.out.println("為空");
}
4 .3 Optional.ifPresent()方法(判讀是否為空并傳回函數)
如果對象非空,則運作函數體 ,源碼:
public void ifPresent(Consumer<? super T> consumer) {
//如果value不為空,則運作accept方法體
if (value != null)
consumer.accept(value);
}
執行個體:
User user = new User();
user.setFirstName("Tom");
Optional.ofNullable(user).ifPresent(u -> System.out.println("姓名"+u.getFirstName()));
4.4 Optional.filter()方法(過濾對象)
filter()方法大緻意思是,接受一個對象,然後對他進行條件過濾,如果條件符合則傳回Optional對象本身,如果不符合則傳回空Optional ,源碼:
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
//如果為空直接傳回this
if (!isPresent())
return this;
else
//判斷傳回本身還是空Optional
return predicate.test(value) ? this : empty();
}
執行個體:
User user = new User();
user.setFirstName("Tom");
Optional.ofNullable(user).filter(u -> u.getFirstName().equeal("Toms"));
4.5 Optional.map()方法(對象進行二次包裝)
map()方法将對應Funcation函數式接口中的對象,進行二次運算,封裝成新的對象然後傳回在Optional中 ,源碼:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
//如果為空傳回自己
if (!isPresent())
return empty();
else {
//否則傳回用方法修飾過的Optional
return Optional.ofNullable(mapper.apply(value));
}
}
執行個體:
User user = new User();
user.setFirstName("Tom");
String optName = Optional.ofNullable(user).map(u -> u.getFirstName()).orElse("firstName為空");
4.6 Optional.flatMap()方法(Optional對象進行二次包裝)
map()方法将對應Optional< Funcation >函數式接口中的對象,進行二次運算,封裝成新的對象然後傳回在Optional中, 源碼:
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));
}
}
執行個體:
User user = new User();
user.setFirstName("Tom");
Optional<Object> optName = Optional.ofNullable(user).map(u -> Optional.ofNullable(u.getFirstName()).orElse("firstName為空"));
4.7 Optional.orElse()方法(為空傳回對象)
如果包裝對象為空的話,就執行orElse方法裡的value,如果非空,則傳回寫入對象 ,源碼:
public T orElse(T other) {
//如果非空,傳回value,如果為空,傳回other
return value != null ? value : other;
}
執行個體:
User user = new User();
user.setFirstName("Tom");
Optional.ofNullable(user).orElse(new User("Tom"));
4.8 Optional.orElseGet()方法(為空傳回Supplier對象)
這個與orElse很相似,入參不一樣,入參為Supplier對象,為空傳回傳入對象的.get()方法,如果非空則傳回目前對象 ,源碼:
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
執行個體:
Optional<Supplier<User>> sup=Optional.ofNullable(User::new);
//調用get()方法,此時才會調用對象的構造方法,即獲得到真正對象
Optional.ofNullable(user).orElseGet(sup.get());
Suppiler是一個接口,是類似Spring的懶加載,聲明之後并不會占用記憶體,隻有執行了get()方法之後,才會調用構造方法建立出對象 建立對象的文法的話就是
Supplier<User> supUser= User::new;
需要使用時
sup
User
.get()
即可
4.9 Optional.orElseThrow()方法(為空傳回異常)
如果為空,就抛出定義的異常,如果不為空傳回目前對象, 源碼:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
執行個體:這個就貼實戰源碼了
Member member = memberService.selectByPhone(request.getPhone());
Optional.ofNullable(member).orElseThrow(() -> new ServiceException("沒有查詢的相關資料"));
4.10 相似方法進行對比分析
可能小夥伴看到這,沒用用過的話會覺得orElse()和orElseGet()還有orElseThrow()很相似,map()和flatMap()好相似 哈哈哈不用着急,都是從這一步過來的,我再給大家總結一下不同方法的異同點 orElse()和orElseGet()和orElseThrow()的異同點
方法效果類似,如果對象不為空,則傳回對象,如果為空,則傳回方法體中的對應參數,是以可以看出這三個方法體中參數是不一樣的 orElse(T 對象) orElseGet(Supplier < T >對象) orElseThrow(異常)
map()和orElseGet的異同點
方法效果類似,對方法參數進行二次包裝,并傳回,入參不同 map(function函數) flatmap(Optional< function >函數)
具體要怎麼用,要根據業務場景以及代碼規範來定義,下面可以簡單看一下我在實戰中怎用使用神奇的Optional
5. Optional使用注意事項
Optional真麼好用,真的可以完全替代if判斷嗎? 我想這肯定是大家使用完之後Optional之後可能會産生的想法,答案是否定的 舉一個最簡單的栗子:例子1:如果我隻想判斷對象的某一個變量是否為空并且做出判斷呢?
User user = new User();
user.setFirstName("");
//普通判斷
if(StringUtils.isNotBlank(user.getFirstName())){
//名稱不為空執行代碼塊
}
//使用Optional做判斷
Optional.ofNullable(user).map(u -> u.getFirstName()).orElse("firstName為空");
我覺得這個例子就能很好的說明這個問題,隻是一個很簡單判斷,如果用了Optional我們還需要考慮包裝值,考慮代碼書寫,考慮方法調用,雖然隻有一行,但是可讀性并不好,如果别的程式員去讀,我覺得肯定沒有if看的明顯
6. jdk1.9對Optional優化
首先增加了三個方法: or()、ifPresentOrElse() 和 stream()。
6.1 ifPresentOrElse(Consumer,Runnable)
ifPresentOrElse() 方法有兩個參數:一個 Consumer 和一個 Runnable。如果對象不為空,會執行 Consumer 的動作,否則運作 Runnable。相比ifPresent()多了OrElse判斷。
ifPresentOrElse(Consumer,Runnable)的語義可以解釋為:ifPresent就Consumer,OrElse就Runnable。這是Java 9 才有的增強方法。
執行個體:Stream流過濾3的倍數,然後通過findFirst找到第一個3的倍數。如果找到一個這樣的值,就print控制台列印出來;如果沒找到一個這樣的值,就輸出"沒有找到3的倍數"
IntStream.of(1, 2, 4)
.filter(i -> i % 3 == 0)
.findFirst()
.ifPresentOrElse(System.out::println, () -> {
System.out.println("沒有找到3的倍數");
});
6.2 Optional.or(Supplier)
or() 與orElse等方法相似,如果對象不為空傳回對象,如果為空則傳回or()方法中預設的值。
執行個體:過濾數組['a', 'b', 'c'],isDigit判斷數組中是否有數字字元,明顯沒有,是以findFirst找不到一個這樣的值。是以生成一個預設值:
Optional.of('0')
char digit = Stream.of('a', 'b', 'c')
.filter(e -> Character.isDigit(e))
.findFirst()
.or(() -> Optional.of('0')).get();
System.out.println(digit); //0
6.3 Optional.stream()
**stream()**将Optional轉換成stream,如果有值就傳回包含值的stream,如果沒值,就傳回空的stream。
執行個體:本例中
Optional.stream()
方法傳回僅包含一個最大值元素的Stream流。
OptionalInt opt1 = IntStream.of(2, 5, 6).max(); //求最大值
OptionalInt opt2 = IntStream.of(1, 3, 7).max(); //求最大值
IntStream.concat(opt1.stream(), opt2.stream()) //将2個流合并
.forEach(System.out::println); //将合并後的流資料列印 6、7