【Java8特性】彙總
文章目錄
-
- Optional類介紹
- Optional類的使用
-
- 建立Optional執行個體
-
- Optional.empty()
- Optional.of()
- Optional.ofNullable()
- 通路 Optional 對象的值
-
- get()
- isPresent()
- ifPresent()
- orElse(T other)
- orElseGet(Supplier<? extends T> other)
- orElse() 和 orElseGet() 的不同之處
- orElseThrow(Supplier<? extends X> exceptionSupplier)
- 過濾值
-
- filter(Predicate<? super T> predicate)
- 轉換值
-
- map()
- flatMap()
- Optional 類的最佳實踐
Optional類介紹
從
Java 8
引入的一個很有趣的特性是 Optional 類。Optional 類主要解決的問題是臭名昭著的空指針異常(
NullPointerException
) —— 每個 Java 程式員都非常了解的異常。
本質上,這是一個包含有可選值的包裝類,這意味着 Optional 類既可以含有對象也可以為空。
Optional 是 Java 實作函數式程式設計的強勁一步,并且幫助在範式中實作。但是 Optional 的意義顯然不止于此。
我們從一個簡單的用例開始。在 Java 8 之前,任何通路對象方法或屬性的調用都可能導緻
NullPointerException
:
在這個小示例中,如果我們需要確定不觸發異常,就得在通路每一個值之前對其進行明确地檢查:
// 查詢國家碼
public void getIsoCode(User user) {
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
}
你看到了,這很容易就變得冗長,難以維護。
為了簡化這個過程,我們來看看用 Optional 類是怎麼做的。從建立和驗證執行個體,到使用其不同的方法,并與其它傳回相同類型的方法相結合,下面是見證 Optional 奇迹的時刻。
public String getIsoCode(User user) {
return Optional.ofNullable(user)
.map(a -> a.getAddress())
.map(a -> a.getCountry())
.map(a -> a.getIsocode())
.map(a -> a.toUpperCase())
.orElse("CHINA");
}
Optional類的使用
Optional類有很多方法,通過
of()
、
ofNullable()
構造Optional執行個體,Optional類擁有
value屬性
,通過
get()
擷取執行個體的value屬性值,通過組合這些方法,最終實作上述解決NullPointerException問題的。下面我們來認識一些基礎的方法,逐漸揭秘。
建立Optional執行個體
Optional.empty()
empty()方法傳回一個空的Optional對象,其
value屬性
為
null
,用法如下。
empty()方法之是以放在第一個介紹是有原因的,因為涉及到
Optional的構造是私有的
,這個知識很重要,我們來看下源碼:
/*
* @since 1.8 标記1.8版本後才有的
*/
public final class Optional<T> {
//内部執行個體化一個Optional執行個體,命名為EMPTY ,全局唯一的
private static final Optional<?> EMPTY = new Optional<>();
//value屬性
private final T value;
//empty()方法實際上 就是傳回之前那個命名為EMPTY 執行個體
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//構造函數,注意是私有的,無法在外部用new通路
private Optional() {
this.value = null;
}
Optional構造方法是私有的,也就是說不允許外部通過new的形式建立對象
。構造方法是供Optional類内部使用。Optional内部維護這個一個value的變量,無參數建構的時候value為null
Optional.empty()傳回的執行個體對象時全局唯一的,多次調用,結果是同一個對象:
@org.junit.Test
public void testEmpty() {
Assert.assertEquals(Optional.empty(), Optional.empty()); //我們調用了2次empty(),并比較equals
Assert.assertTrue(Optional.empty() == Optional.empty()); //比較對象位址
}
Optional.of()
建立value屬性為指定值的 Optional執行個體,就是用一個Optional對象,包裹了給定的值。用法如下:
通過of方法所構造出的Optional對象有如下特點:
- 當指定值為空時,會報NullPointerException異常
- 當指定值不為空時,正常構造Optional對象
我們通過源碼來分析下原因:
public final class Optional<T> { //Optional類
public static <T> Optional<T> of(T value) {
return new Optional<>(value); //調用私有的構造函數
}
private Optional(T value) {
this.value = Objects.requireNonNull(value); //調用Objects.requireNonNull限制指定值非空
}
}
public final class Objects { //Objects工具類
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException(); //如果為空,則抛出異常
return obj;
}
注意: 隻有你确定對象100%不為 null 的時候才能使用 of(),否則會報NullPointerException
Optional.ofNullable()
建立value屬性為指定值的 Optional執行個體,如果指定為空,則傳回一個空的Optional執行個體。
ofNullable用法和of()相似,但是差別在于ofNullable對指定值無限制,即使傳入一個null值,也不會報錯,通過源碼來分析原因:
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value); //給定值為空,傳回空的Optional執行個體,不為空,則複用of()構造一個新的執行個體
}
如果對象即可能是 null 也可能是非 null,你就應該使用 ofNullable() 方法,避免用of()方法
通路 Optional 對象的值
我們在前面知道Optional就是包裹了給定的值,進行了封裝,自身有個
value屬性
,那麼擷取對象的值,就是擷取
value屬性
。
get()
從 Optional 執行個體中取回
value屬性
@Test
public void whenCreateOfNullableOptional_thenOk() {
String name = "John";
Optional<String> opt = Optional.ofNullable(name); //建構一個Optional 執行個體
assertEquals("John", opt.get()); //擷取Optional 執行個體的值
}
不過,需要注意的是,當value屬性為空時會報錯:
@Test(expected = NoSuchElementException.class)
public void testGetThrowException() {
Optional.empty().get();
}
通過源碼分析下原因:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present"); //判斷value屬性為空時抛出異常
}
return value; //傳回value屬性
}
為了避免出錯,可以在調用之前先驗證是否有值,
get()
應運而生
isPresent()
isPresent()
判斷
value屬性
值是否存在。
value屬性
== null 表示不存在,非null表示存在。
例子:
@org.junit.Test
public void testIsPresent() {
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Assert.assertEquals(optional1.isPresent(), true); //存在
Assert.assertEquals(optional2.isPresent(), false); //不存在
}
注意:方法的 字首是is,不是if
,避免和下面的ifPresent()混淆
由于null、空字元串比較容易混淆 “存在”的含義,我們上源碼:
public boolean isPresent() {
return value != null; //與null進行比較
}
由源碼得出結論:空字元串表示存在
ifPresent()
方法定義:
ifPresent()不是擷取值,該方法沒有傳回值。之是以放在此處介紹,是因為名稱和前面的isPresent()容易混淆。
如果option對象的value屬性值不是null,則調用consumer對象,并将value屬性值作為入參傳入,否則不調用。Consumer可以了解為一個Lambda 表達式(Lambda 章節專門講解Consumer用法),并且限定隻有一個入參:
Optional<User> opt = Optional.ofNullable(user);
opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));
這個例子中,隻有 user 使用者不為 null 的時候才會執行斷言。
orElse(T other)
結構為:
orElse(T other)是一個工具方法,作用類似
get()
,可以擷取
value屬性
的值,之是以說是一個工具方法,是因為當value屬性值為空時,傳回的值是指定的other參數值:
@org.junit.Test
public void testOrElse() {
Optional<String> optional1 = Optional.ofNullable("zhangsan"); //option執行個體非空
Optional<String> optional2 = Optional.ofNullable(null); //option執行個體為空
Assert.assertEquals(optional1.orElse("lisi"), "zhangsan"); //option執行個體非空,傳回value屬性值
Assert.assertEquals(optional2.orElse("lisi"), "lisi"); //option執行個體為空,則傳回指定的新值
}
源碼也很簡單:
public T orElse(T other) {
return value != null ? value : other; //value不為空,則傳回value;為空則傳回other
}
這個方法很重要,比如我們經常在處理傳回值時,如果值是一個null,我們希望轉化為空字元串,這樣傳回值在外部被調用時就不會報空指針了
public String getValue(){ //正常方式
String result = doSomething();
if(result==null){
return ""; //把null轉化為空字元串
}
}
public String getValue(){ //Optional方式
String result = doSomething();
return Optional.of(result).orElse(""); //自動轉化為空字元串
}
orElseGet(Supplier<? extends T> other)
用法為:
從字面上就能看出作用與
orElse()
類似,也是擷取
value屬性
值,但行為略有不同。這個方法會在value屬有值的時候傳回value屬值,如果沒有值,它會執行作為參數傳入的
Supplier(供應者) 函數式接口
,并将傳回其執行結果:
@org.junit.Test
public void testOrElseGet() {
Optional<Integer> optional1 = Optional.ofNullable(1); //option執行個體非空,并且value屬性值為1
Optional<Integer> optional2 = Optional.ofNullable(null); //option執行個體為空
Assert.assertEquals(optional1.orElseGet(() -> {
return Integer.valueOf(1000); //表達式,執行結果為1000
}), Integer.valueOf(1)); //option執行個體非空,傳回value屬性值1
Assert.assertEquals(optional2.orElseGet(() -> {
return Integer.valueOf(1000); //表達式,執行結果為1000
}), Integer.valueOf(1000)); //option執行個體為空,則傳回Supplier Lambda結果,1000
}
orElse() 和 orElseGet() 的不同之處
orElse() 和 orElseGet() 的不同之處在于前者傳入一個值,後者是個Lambda表達式。然而,如果傳入值的方式是通過調用其他方法形式就會産生一點細微差別,我們來解釋下什麼是調用其他方法形式傳值:
public class Test {
public String functionA() { //方法A
return ..;
}
public void functionB(){
Optional opt = ...
opt.orElse(functionA()) //orElse()調用方法A傳入值
}
public void functionC(){
Optional opt = ...
opt.orElseGet(()-> functionA()) //orElseGet調用方法A傳入值
}
}
差別在與,如果傳入的value不為空,orElse()中的方法仍然要執行,而orElseGet() 不執行。
測試value屬性為空情況,二者都會執行:
@org.junit.Test
public void testOrElseAndOrElseGet() {
Optional<String> optional1 = Optional.ofNullable(null); //value屬性為空
Optional<String> optional2 = Optional.ofNullable(null); //value屬性為空
optional1.orElse(getName()); //會執行getName()
System.out.println("--------------");
optional2.orElseGet(() -> getName()); //會執行getName()
}
public String getName() {
System.out.println("getName() executed !"); //列印執行辨別
return "lisi";
}
我們看下輸出:
getName() executed ! //orElse觸發列印的
--------------
getName() executed ! //orElseGet觸發列印的
測試value屬性不為空情況,orElse會執行,orElseGet不執行:
@org.junit.Test
public void testOrElseAndOrElseGet2() {
Optional<String> optional1 = Optional.ofNullable("lisi"); //value屬性不為空
Optional<String> optional2 = Optional.ofNullable("lisi"); //value屬性不為空
optional1.orElse(getName()); //會執行getName()
System.out.println("--------------");
optional2.orElseGet(() -> getName()); //不會執行getName()
}
我們看下輸出:
getName() executed !
--------------
在執行較密集的調用時,比如調用 Web 服務或資料查詢,這個差異會對性能産生重大影響。
orElseThrow(Supplier<? extends X> exceptionSupplier)
它會在對象為空的時候抛出異常,而不是傳回某個值,并且該異常由入參傳入:
@Test(expected = IllegalArgumentException.class) //期望抛IllegalArgumentException異常
public void testOrElseThrow() {
Optional.ofNullable(null).orElseThrow(() -> new IllegalArgumentException()); // 我們定義了一個空的執行個體,orElseThrow傳入一個異常
}
這個方法讓我們有更豐富的語義,可以決定抛出什麼樣的異常,而不總是抛出 NullPointerException。
過濾值
filter(Predicate<? super T> predicate)
filter()方法作用是當Optional中value屬性不為空的時,對Optional中的值進行過濾。
注意:filter()方法傳回值仍是一個Optional對象,并且對象value屬性可能為空,而不是一個具體的值。
filter() 接受一個 Predicate 參數,這是個
判斷表達式
,判斷結果為true時,保留目前執行個體,判斷結果為false時,傳回一個空的Optional。
@org.junit.Test
public void testFilter() {
Optional<String> optional1 = Optional.ofNullable("abcd"); //非空執行個體
Optional<String> optional2 = Optional.ofNullable(null); //空執行個體
//非空執行個體場景
Assert.assertEquals(optional1.filter(a -> a.startsWith("ab")).get(), "abcd"); //表達式結果為true,保留目前執行個體
Assert.assertEquals(optional1.filter(a -> a.startsWith("AB")), Optional.empty()); //表達式結果為fasle,傳回空執行個體
//空執行個體場景,直接傳回空對象
Assert.assertEquals(optional2.filter((a) -> (1 == 1)), Optional.empty());
}
轉換值
map()和flatMap()對Optional中的對象進行轉換值的操作,這兩個方法唯一的差別就是接受的參數不同。
map()
對Optional中儲存的值進行函數運算,并傳回新的Optional(可以是任何類型)。入參是一個mapping函數,Optional的value屬性作為mapping函數入參。
注意:map()傳回值是Optional對象,mapper函數傳回值是任意值,最終會被封裝成Optional對象
我們看個例子:
@org.junit.Test
public void testMap() {
Optional<String> optional1 = Optional.ofNullable("ab"); // 非空執行個體
Optional<String> optional2 = Optional.ofNullable(null); //空執行個體
Assert.assertEquals(optional1.map(a -> a + "cd").get(), "abcd"); //函數作用是拼接字元串
Assert.assertEquals(optional2.map(a -> a + "cd"), Optional.empty()); //空執行個體傳回空執行個體,不會調用函數表達式
}
看下源碼:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper); //傳入的函數必須存在,不能是null
if (!isPresent())
return empty(); //如果屬性值為空,直接傳回
else {
return Optional.ofNullable(mapper.apply(value)); //計算後的值,如果為空,通過ofNullable傳回empty()
}
}
由源碼,總結特點:
-
傳入的函數不能為null,否則報NullPointerException
Objects.requireNonNull(mapper)起的作用
- 如果執行個體本身為空,不會執行函數,直接傳回空
-
如果執行函數的計算結果為null,會傳回empty()
對mapping傳回值調用ofNullable封裝起的作用
-
mapper函數傳回值是任意值,最終會被封裝成Optional對象
對mapping傳回值調用ofNullable封裝起的作用
flatMap()
flatMap方法與map方法類似,也是對Optional中儲存的值進行函數運算,差別在于
mapping
函數的傳回值不同。
方法的
map()
函數傳回值可以是任何類型
mapping
,而
T
方法的
flatMap()
函數必須是
mapping
。
Optional
我們來看個例子,這個例子和map()非常相似,隻是對mapping傳回值進行封裝:
@org.junit.Test
public void testFlatMap() {
Optional<String> optional1 = Optional.ofNullable("ab"); //建構一個非空執行個體
Optional<String> optional2 = Optional.ofNullable(null); // 建構一個空執行個體
Assert.assertEquals(optional1.flatMap(a -> Optional.of(a + "cd")).get(), "abcd"); //接收一個mapping函數,進行拼接,并手動封裝成optional執行個體
Assert.assertEquals(optional2.flatMap(a -> Optional.of(a + "cd")), Optional.empty()); //空執行個體直接傳回空執行個體
}
驗證mapping傳回值不能為null:
@org.junit.Test(expected = NullPointerException.class) //期望抛出異常
public void testFlatMap_ReturnNotNull() {
Optional<String> optional1 = Optional.ofNullable("ab");
optional1.flatMap(a -> {
return null; //故意傳回null
});
}
看下源碼:
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty(); //自身為空,直接傳回空Optional
else {
return Objects.requireNonNull(mapper.apply(value)); //mapping 傳回結果不能是null,可以是一個空的Optional執行個體
//由于mapping結果未經ofNullable封裝,是以需要mapping自身封裝一個Optional對象
}
}
由源碼,總結特點:
-
mapping傳回值是個Option對象
flatMap最終傳回值是一個Option對象,由于mapping結果未經ofNullable封裝,是以需要mapping自身封裝一個Option對象。這是與map()最大差別的根本原因。
-
mapping傳回值不能是null
由于mapping結果傳回值被Objects.requireNonNull檢查,
是以格外注意傳回值一定不能是null,可以傳回一個空的Optional
- 如果執行個體本身為空,不會執行函數,直接傳回空Optional
Optional 類的最佳實踐
經過上面的鋪墊,我們再來回顧開篇的問題,為何可以簡便的解決空指針問題?
User.java
public class User {
private Address address;//省略get,set方法
Address.java
public class Address {
private Country country; //省略get,set方法
Country.java
public class Country {
private String isocode;//省略get,set方法
TestOption.java
public String getIsoCode2(User user) {
return Optional.ofNullable(user)
.map(a -> a.getAddress())
.map(a -> a.getCountry())
.map(a -> a.getIsocode())
.map(a -> a.toUpperCase())
.orElse("CHINA");
}
@Test
public void test() {
//#1 user為null,直接越過中間步驟,根據orElse,得到預設值
User user = null;
Assert.assertEquals(getIsoCode(user), "CHINA");
//#2 user非null,但是address屬性是null,直接越過中間步驟,根據orElse,得到預設值
user = new User();
Assert.assertEquals(getIsoCode(user), "CHINA");
//#3 address屬性非null,但是country屬性是null,根據orElse,得到預設值
Address address = new Address();
user.setAddress(address);
Assert.assertEquals(getIsoCode(user), "CHINA");
//#4 country非null,那麼最終結果就是其值
Country country = new Country();
country.setIsocode("Usa");
address.setCountry(country);
Assert.assertEquals(getIsoCode(user), "USA");
}
我們來分析一下上面的單元測試 #1步驟:
-
執行 Optional.ofNullable(user)
ofNullable對user進行封裝,得到一個Optional.empty執行個體
-
執行 .map(a -> a.getAddress())
由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體
-
執行.map(a -> a.getCountry())
由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體
-
執行 .map(a -> a.getIsocode())
由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體
-
執行 .map(a -> a.toUpperCase())
由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體
-
執行 .orElse(“CHINA”);
orElse作用是傳回一個值,這個是最終值,傳給return 語句,由于此時Optional對象自身為空執行個體,orElse傳回給定的預設值,即
。CHINA
簡單來說,通過map,把本來為null的屬性轉化為一個Optional.empty執行個體,在鍊式寫法中,空執行個體調用方法map()不會報錯,并且繼續獲的一個Optional.empty執行個體,直至鍊的尾部,通過orElse()收尾。
如果覺得對您有幫助,請不要吝惜您點贊和留言,呵呵,寫的蠻辛苦的。