導航
引例
說起NullPointerException你肯定不會陌生,因爲它大概是我們日常開發中碰到最多的問題。爲了避免空指針異常的出現,我們常常需要做很多的邏輯判斷。下面通過一個例子演示我們平常是如何應對空指針問題的。
蘋果有不同的種類,而不同的品牌會同生產同一種蘋果,蘋果、種類和品牌三者可以構成一種嵌套關係。
public class Apple {
private Kind kind;
public Kind getKind() {
return kind;
}
public void setKind(Kind kind) {
this.kind = kind;
}
@Override
public String toString() {
return "Apple [kind=" + kind + "]";
}
}
public class Kind {
private Brand brand;
public Brand getBrand() {
return brand;
}
public void setBrand(Brand brand) {
this.brand = brand;
}
@Override
public String toString() {
return "Kind [brand=" + brand + "]";
}
}
public class Brand {
private String brandName;
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
@Override
public String toString() {
return "Brand [brandName=" + brandName + "]";
}
}
可能有的人得到一個蘋果,就直接查看這個蘋果的品牌。例如這樣:
String brandName = apple.getKind().getBrand().getBrandName();
這樣的代碼極有可能出現空指針問題,當然大多數人會採取一定的手段避免NullPointerException。下面演示兩種做法:
public class TestNullPointer {
public static void main(String[] args) {
System.out.println(getAppleBrand1(null));
System.out.println(getAppleBrand2(null));
}
// 方式一
private static String getAppleBrand1(Apple apple) {
String result = "empty";
if(apple != null) {
Kind kind = apple.getKind();
if (kind != null) {
Brand brand = kind.getBrand();
if (brand != null) {
result = brand.getBrandName();
}
}
}
return result;
}
// 方式二
private static String getAppleBrand2(Apple apple) {
String defaultValue = "empty";
if(apple == null)
return defaultValue;
Kind kind = apple.getKind();
if(kind == null)
return defaultValue;
Brand brand = kind.getBrand();
if(brand == null)
return defaultValue;
return brand.getBrandName();
}
}
打印結果如下:
empty
empty
上面給出的兩種做法都使用了大段的判斷邏輯避免空指針問題,這讓我們的代碼的可讀性變得很差。那麼還有更好的方法解決這個問題嗎?答案是肯定的,我們可以使用Java8提供Optional免去代碼中大段的判斷邏輯,讓代碼變的更加簡潔。
首先我們需要重新設計Apple和Brand兩個類:
public class Apple2 {
private Kind2 kind2;
public Optional<Kind2> getKind2() { // 使用Optional封裝返回值
return Optional.ofNullable(kind2);
}
public void setKind2(Kind2 kind2) {
this.kind2 = kind2;
}
@Override
public String toString() {
return "Apple2 [kind2=" + kind2 + "]";
}
}
public class Kind2 {
private Brand brand;
public Optional<Brand> getBrand() { // 使用Optional封裝返回值
return Optional.ofNullable(brand);
}
public void setBrand(Brand brand) {
this.brand = brand;
}
@Override
public String toString() {
return "Kind2 [brand=" + brand + "]";
}
}
在新設計的Apple2和Brand2類中,我們用Optional封裝兩個類中get()方法的返回值。重新實現TestNullPointer中的方法:
public class TestOptional1 {
public static void main(String[] args) {
System.out.println(getAppleBrand(null));
}
private static String getAppleBrand(Apple2 apple) {
return Optional.ofNullable(apple)
.flatMap(Apple2::getKind2)
.flatMap(Kind2::getBrand)
.map(Brand::getBrandName)
.orElse("empty");
}
}
打印結果如下(代碼中出現的::操作爲方法引用):
empty
上面的代碼中我們並沒有進行任何顯式判斷空值的操作,卻仍然達到了避免空指針的目的。
所以Optional讓我們避免了這樣的問題:爲了判斷空值而不得不顯式使用大段的判斷邏輯,降低代碼的可讀性。 換而言之,Optional可以讓你的代碼變得更加優雅。
Optional
容器類
Optional是JDK8提供的一個類。它可以簡單的封裝一個對象,使用成員變量(value)記錄該對象並提供一系列方法來操作該對象。簡單來說,Optional就是一個容器類。
public final class Optional<T> {
private final T value;
...
}
有值狀態與無值狀態
Optional有兩種狀態,一種是無值狀態(value爲null,一個空的Optional對象),另一種是有值狀態(value不爲null)。
可能你會覺得奇怪——一個空的Optional對象和null有區別嗎?
在語義上你可以把它們當作一回事兒,但是實際使用上它們之間的差別很大:如果你嘗試解引用一個null,一定會觸發NullPointerException;不過使用空的Optional對象卻不會出現這種問題,因爲它依然是一個有效的對象。
三種方法創建Optional對象
Optional的構造方法都是私有方法,這意味着如果我們想要創建Optional對象就只能通過該類提供的靜態方法。Optional提供了三個靜態方法創建一個Optional對象:empty(),of()和ofNullable()。下面是Optional的部分源碼:
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>(); // 調用空參構造方法
private final T value;
private Optional() { // 空參構造方法
this.value = null;
}private Optional(T value) { // 有參構造方法
this.value = Objects.requireNonNull(value);
}public static<T> Optional<T> empty() { // 返回EMPTY
Optional<T> t = (Optional<T>) EMPTY;
return t;
}public static <T> Optional<T> of(T value) { // 調用有參構造
return new Optional<>(value);
}public static <T> Optional<T> ofNullable(T value) { // 根據傳入值是否爲null,選擇調用of()方法或empty()方法
return value == null ? empty() : of(value);
}
...
}
從源碼中可以看到,Optional類在初始化階段就會調用空參構造方法創建靜態常量EMPTY——一個空的Optional對象。三個靜態方法的作用如下:
- empty():返回EMPTY。
- of():調用有參構造方法。
- ofNullable():根據參數選擇執行empty()方法或者of()方法。
下面通過一個例子演示三個方法:
public class TestOptional2 {
public static void main(String[] args) {
Optional<Object> op = Optional.empty();
System.out.println(op);
// 調用empty()方法創建Optional對象時,可以指定類型
Optional<Apple> empty = Optional.<Apple>empty();
Optional<Apple> op2 = Optional.of(new Apple());
// Optional<Object> op3 = Optional.of(null); // 報錯: java.lang.NullPointerException
System.out.println(op2);
Optional<Object> op4 = Optional.ofNullable(null);
System.out.println(op4);
}
}
打印結果如下:
Optional.empty
Optional[Apple [kind=null]]
Optional.empty
創建Optional對象時有兩個注意點:
- 調用empty()方法創建Optional對象時,默認類型爲Object。不過你也可以指定類型,例如Optional.<Apple>empty()。
- 調用of()方法時,若傳入的參數爲null會拋出NullPointerException。
Optional操作詳解
四種取值方式
當我們得到一個Optional對象,應該怎麼獲取它的值呢?Optional類中提供四個方法:
- get():最簡單但又最不安全的方法,若Optional對象有值返回值,無值拋出NoSuchElementException異常。
- orElse():參數爲一個實例對象,若Optional對象有值返回值,無值返回該對象。
- orElseGet():將Supplier接口作爲參數,若Optional對象有值返回值,無值返回調用Supplier生成的實例。
- orElseThrow():將Supplier接口作爲參數,若Optional對象有值返回值,無值拋出調用Supplier的生成的異常。
下面通過一個例子演示:
public class TestOptional3 {
public static void main(String[] args) {
Optional<Apple> op = Optional.<Apple>ofNullable(null);
// Apple apple1 = op.get(); // NoSuchElementException
Apple apple2 = op.orElse(new Apple());
System.out.println(apple2);
Apple apple3 = op.orElseGet(Apple::new);
System.out.println(apple3);
// Apple apple4 = op.orElseThrow(RuntimeException::new); // RuntimeException
}
}
打印結果如下:
Apple [kind=null]
Apple [kind=null]
這些方法都比較容易理解,但是有兩個方法在使用上會有略微的區別——orElse()和orElseGet()。若Optional對象是無值狀態的,這兩個方法並沒有區別;但是如果Optional對象有值,區別就會出現。
爲了更好的演示效果,對Apple類出一點修改。在Apple類中加入構造方法:
public Apple() {
System.out.println("create apple");
}
用有值狀態的Optional對象分別調用orElse()和orElseGet()方法:
public class TestOptional4 {
public static void main(String[] args) {
Apple apple = new Apple();
Optional<Apple> op = Optional.ofNullable(apple);
System.out.println("======================");
op.orElseGet(Apple::new);
System.out.println("======================");
op.orElse(new Apple()); // 即使Optional對象有值,orElse()方法還是會創建對象
}
}
打印結果如下:
create apple
======================
======================
create apple
通過打印結果我們可以發現:即使Optional對象有值,調用orElse()方法時還是會創建一個Apple對象;而orElseGet()方法在Optional對象是有值狀態下並不會調用傳入的Lambda表達式創建對象。
isPresent和ifPresent
如果Optional是無值狀態的,調用get()方法時會出現NoSuchElementException異常。爲了規避這個問題Optional提供isPresent()和ifPresent()方法。
- isPresent():返回布爾值表示Optional是否爲有值狀態。
- ifPresent():將Consumer接口作爲參數,若Optional爲有值狀態則調用該接口。
見下面一個例子:
public class TestOptional5 {
public static void main(String[] args) {
Optional<Apple> op = Optional.ofNullable(new Apple());
// 兩種寫法等價
if(op.isPresent())
System.out.println(op.get());
op.ifPresent(System.out::println);
}
}
打印結果如下:
create apple
Apple [kind=null]
Apple [kind=null]
代碼中兩種寫法是等價的,第二種寫法更高明一些。因爲第一種寫法完全沒有體現出Optional的作用,它與直接判null並沒有本質上的區別。
filter、map與flatMap
filter()方法可以過濾Optional對象中不符合條件的值,該方法使用Predicate接口作爲參數:
public Optional<T> filter(Predicate<? super T> predicate)
調用此方法時,若Optional對象無值直接返回該Optional對象;反之,對Optional中的值應用predicate操作。若該操作執行結果爲true,將此Optional對象返回,否則返回一個空的Optional對象。
public class TestOptional6 {
public static void main(String[] args) {
Predicate<String> predicate = item -> item.length() > 4; // Lambada表達式
Optional<String> op1 = Optional.ofNullable("hello");
Optional<String> result1 = op1.filter(predicate); // 將此Optional對象返回
System.out.println(result1.get());
Optional<String> op2 = Optional.<String>empty();
Optional<String> result2 = op2.filter(predicate); // 返回一個空的Optional
System.out.println(result2.orElse("no element"));
}
}
打印結果如下:
hello
no element
map()方法可以將Optional對象中的值進行映射,該方法將Function接口作爲參數:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
調用此方法時,若Optional對象無值返回一個空的Optional對象;反之,對Optional對象中的值應用mapper操作,將該操作的返回值封裝爲Optional對象並返回此Optional對象。
public class TestOptional7 {
public static void main(String[] args) {
Function<String, Integer> function = String::length; // 方法引用
Optional<String> op1 = Optional.ofNullable("hello");
Optional<Integer> result1 = op1.map(function);
System.out.println(op1.get() + " length is " + result1.orElse(0));
Optional<String> op2 = Optional.<String>empty();
Optional<Integer> result2 = op2.map(function);
System.out.println(result2);
}
}
打印結果如下:
hello length is 5
Optional.empty
上面的例子中,mapper操作的返回值是Integer類型,所以將返回值封裝爲Optional對象並沒有什麼問題。但是如果mapper操作的返回值是本身就是Optional類型的,此時再將返回值封裝爲Optional對象,結果就不盡人意了。
public class TestOptional8 {
public static void main(String[] args) {
Optional<Apple2> op = Optional.of(new Apple2());
// public Optional<Kind2> getKind2()
Optional<Optional<Kind2>> map = op.map(Apple2::getKind2);
}
}
通過上面的例子可以看到,如果mapper操作的返回值類型是Optional,調用map()方法得到的結果類型就是Optional嵌套的類型。所以針對這種情況,我們需要使用flatMap()方法:
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
flatMap()方法和map()方法的不同之處在於:調用map()方法會先對Optional對象中的值應用mapper操作,然後將該操作返回的值封裝成一個Optional對象;而調用flatMap()方法只對Optional對象中的值應用mapper操作,該操作的返回值就是Optional類型,不需要再對返回值進行封裝。
public class TestOptional9 {
public static void main(String[] args) {
Optional<Apple2> op = Optional.of(new Apple2());
Optional<Kind2> op2 = op.flatMap(Apple2::getKind2);
Optional<Brand> op3 = op2.flatMap(Kind2::getBrand);
Optional<String> op4 = op3.map(Brand::getBrandName);
System.out.println(op4.orElse("no element"));
}
}
上面的例子就是引例中TestOptional1的詳細版本。
注意點
1、Optional不能序列化,最好避免將Optional用作類的字段(field)類型。
public class Person {
private Optional<String> name; // 應該避免將類的字段定義成Optional類型
...
}
2、推薦將Optional作爲方法的返回值類型,例如Stream中的findFirst()方法就是如此。
Optional<T> findFirst();
3、如果僅僅用作判空,那麼不應該使用Optional,直接判null就好了。
Apple apple = getApple();
Optional.ofNullable(apple).ifPresent(System.out::println); // 這樣使用Optional沒有體現Optional的價值
if (apple != null) System.out.println(apple); // 不如直接判null
4、更多Optional的使用技巧請參考:使用Optional的正確姿勢
參考:
https://www.cnblogs.com/zhangboyu/p/7580262.html