前言
只有光頭才能變強
前兩天帶女朋友去圖書館了,隨手就給她來了一本《與孩子一起學編程》的書,於是今天就給女朋友講解一下什么是Optional類。
- 至於她能不能看懂,那肯定是看不懂的。(學到變量/for循環的女人怎么能看懂呢)
不知道大家還記得上一篇《阿里巴巴 Java開發手冊》讀后感不,當時閱讀到空指針異常(NPE)時,書上提到JDK 8有個Optional類供我們使用,該類可以盡可能地防止出現空指針異常(NPE)。
文本力求簡單講清每個知識點,希望大家看完能有所收獲
一、基礎鋪墊
我們都知道JDK 8最重要的新特性是Lambda表達式,這個可以讓我們簡化非常多的代碼編寫,不知道大家會使用了沒有。這里我簡單跟大家來回顧一下~
1.1Lambda簡化代碼例子
下面就以幾個例子來看看Lambda表達式是怎么簡化我們代碼的編寫的。
首先我們來看看創建線程:
public static void main(String[] args) {
// 用匿名內部類的方式來創建線程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("公眾號:Java3y---回復1進群交流");
}
});
// 使用Lambda來創建線程
new Thread(() -> System.out.println("公眾號:Java3y---回復1進群交流"));
}
再來看看遍歷Map集合:
public static void main(String[] args) {
Map<String, String> hashMap = new HashMap<>();
hashMap.put("公眾號", "Java3y");
hashMap.put("交流群", "回復1");
// 使用增強for的方式來遍歷hashMap
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
// 使用Lambda表達式的方式來遍歷hashMap
hashMap.forEach((s, s2) -> System.out.println(s + ":" + s2));
}
在List中刪除某個元素
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java3y");
list.add("3y");
list.add("光頭");
list.add("帥哥");
// 傳統的方式刪除"光頭"的元素
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
if ("光頭".equals(iterator.next())) {
iterator.remove();
}
}
// Lambda方式刪除"光頭"的元素
list.removeIf(s -> "光頭".equals(s));
// 使用Lambda遍歷List集合
list.forEach(s -> System.out.println(s));
}
從上面的例子我們可以看出,Lambda表達式的確是可以幫我們簡化代碼的。
1.1函數式接口
使用Lambda表達式,其實都是建立在函數式接口上的。我們看看上面的代碼的接口:
創建多線程的Runnable接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
遍歷HashMap的BiConsumer接口:
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
在List中刪除元素的Predicate接口:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
函數式接口的特點:由@FunctionalInterface
注解標識,接口有且僅有一個抽象方法!
1.2Lambda簡單講解
或許我們一開始看到Lambda的時候,發現Lambda表達式的語法有點奇葩,甚至有點看不懂。沒事,這里3y給大家用圖的形式畫一畫:
以Runnable接口來舉例:
再不濟,我們在用IDE的時候,可以提示出Lambda表達式的語法的,這樣可以幫我們快速上手Lambda表達式:
說白了,我們使用Lambda表達式的架子是這樣的()->{}
,具體的時候看看函數式接口的抽象方法要求就可以了,再不濟就使用IDE智能提示。
1.3泛型回顧
比如說public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
這個聲明,你看懂了嗎?
// 接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
在泛型的上限和下限中有一個原則:PECS(Producer Extends Consumer Super)
- 帶有子類限定的可以從泛型讀取【也就是--->(? extend T)】-------->Producer Extends
- 帶有超類限定的可以從泛型寫入【也就是--->(? super T)】-------->Consumer Super
解析:傳入的參數是泛型 T 或者其父類,返回值是U或其子類。
具體可參考:
二、Optional類
一句話介紹Optional類:使用JDK8的Optional類來防止NPE(空指針異常)問題。
接下來我們看看文檔是怎么說的:
A container object which may or may not contain a non-null value.Additional methods that depend on the presence or absence of a contained value are provided
它是一個容器,裝載着非NULL元素(或者沒有裝載元素),提供了一系列的方法供我們判斷該容器里的對象是否存在(以及后續的操作)。
Optional類的方法結構圖:
2.1創建Optional容器
我們先來看看Optional的屬性以及創建Optional容器的方法:
// 1、創建出一個Optional容器,容器里邊並沒有裝載着對象
private static final Optional<?> EMPTY = new Optional<>();
// 2、代表着容器中的對象
private final T value;
// 3、私有構造方法
private Optional() {
this.value = null;
}
// 4、得到一個Optional容器,Optional沒有裝載着對象
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
// 5、私有構造方法(帶參數),參數就是具體的要裝載的對象,如果傳進來的對象為null,拋出異常
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
// 5.1、如果傳進來的對象為null,拋出異常
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
// 6、創建出Optional容器,並將對象(value)裝載到Optional容器中。
// 傳入的value如果為null,拋出異常(調用的是Optional(T value)方法)
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
// 創建出Optional容器,並將對象(value)裝載到Optional容器中。
// 傳入的value可以為null,如果為null,返回一個沒有裝載對象的Optional對象
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
所以可以得出創建Optional容器有兩種方式:
- 調用ofNullable()方法,傳入的對象可以為null
- 調用of()方法,傳入的對象不可以為null,否則拋出NullPointerException
下面我們簡單就可以看看用法了:
現在我有一個User對象,這里用到了Lombok,有興趣的同學可去學學了解一下:兩個月的Java實習結束,繼續努力
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private Short age;
}
測試:
public static void main(String[] args) {
User user = new User();
User user1 = null;
// 傳遞進去的對象不可以為null,如果為null則拋出異常
Optional<User> op1 = Optional.of(user1);
// 傳遞進去的對象可以為null,如果為null則返回一個沒有裝載對象的Optional容器
Optional<User> op2 = Optional.ofNullable(user);
}
2.2Optional容器簡單的方法
// 得到容器中的對象,如果為null就拋出異常
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
// 判斷容器中的對象是否為null
public boolean isPresent() {
return value != null;
}
// 如果容器中的對象存在,則返回。否則返回傳遞進來的參數
public T orElse(T other) {
return value != null ? value : other;
}
這三個方法是Optional類比較常用的方法,並且是最簡單的。(因為參數不是函數式接口)
下面我們繼續看看用法:
public static void main(String[] args) {
User user = new User();
User user1 = null;
Optional<User> op1 = Optional.ofNullable(user);
System.out.println(op1.isPresent());
System.out.println(op1.get());
System.out.println(op1.orElse(user1));
}
結果很明顯,因為我們的user是不為null的:
我們調換一下順序看看:
public static void main(String[] args) {
User user = new User();
User user1 = null;
Optional<User> op1 = Optional.ofNullable(user1);
System.out.println(op1.isPresent());
System.out.println(op1.orElse(user));
System.out.println(op1.get());
}
2.3Optional容器進階用法
當然了,我們到目前為止看起來Optional類好像就這么一回事了,這樣代碼寫起來還不如我自己判斷null呢...
我們對比一下:
我們可以發現,手動判斷是否為null好像還更方便簡潔一點呢。
所以,我們帶函數式接口的方法登場了!
2.3.1ifPresent方法
首先來看看ifPresent(Consumer<? super T> consumer)
方法
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
如果容器中的對象存在,則調用accept方法,比如說:
public static void main(String[] args) {
User user = new User();
user.setName("Java3y");
test(user);
}
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果存在user,則打印user的name
optional.ifPresent((value) -> System.out.println(value.getName()));
// 舊寫法
if (user != null) {
System.out.println(user.getName());
}
}
2.3.2orElseGet和orElseThrow方法
直接看源碼:
// 如果對象存在,則直接返回,否則返回由Supplier接口的實現用來生成默認值
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 如果存在,則返回。否則拋出supplier接口創建的異常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
例子:
public static void main(String[] args) {
User user = new User();
user.setName("Java3y");
test(user);
}
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果存在user,則直接返回,否則創建出一個新的User對象
User user1 = optional.orElseGet(() -> new User());
// 舊寫法
if (user != null) {
user = new User();
}
}
總的來說跟我們上面所講的orElse()
差不多,只不過它可以通過Supplier接口的實現來生成默認值。
2.3.3filter方法
直接看源碼:
// 如果容器中的對象存在,並且符合過濾條件,返回裝載對象的Optional容器,否則返回一個空的Optional容器
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
// 接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
返回Optional對象我們就可以實現鏈式調用了!
例子:
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果容器中的對象存在,並且符合過濾條件,返回裝載對象的Optional容器,否則返回一個空的Optional容器
optional.filter((value) -> "Java3y".equals(value.getName()));
}
2.3.4map方法
直接看源碼:
// 如果容器的對象存在,則對其執行調用mapping函數得到返回值。然后創建包含mapping返回值的Optional,否則返回空Optional。
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));
}
}
// 接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
例子:
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果容器的對象存在,則對其執行調用mapping函數得到返回值。然后創建包含mapping返回值的Optional,否則返回空Optional。
optional.map(user1 -> user1.getName()).orElse("Unknown");
}
// 上面一句代碼對應着最開始的老寫法:
public String tradition(User user) {
if (user != null) {
return user.getName();
}else{
return "Unknown";
}
}
2.3.5flatMap方法
直接看源碼:
// flatMap方法與map方法類似,區別在於apply函數的返回值不同。map方法的apply函數返回值是? extends U,而flatMap方法的apply函數返回值必須是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));
}
}
2.3.6總結
再來感受一下Optional的魅力
public static void main(String[] args) {
User user = new User();
user.setName("Java3y");
System.out.println(test(user));
}
// 以前的代碼v1
public static String test2(User user) {
if (user != null) {
String name = user.getName();
if (name != null) {
return name.toUpperCase();
} else {
return null;
}
} else {
return null;
}
}
// 以前的代碼v2
public static String test3(User user) {
if (user != null && user.getName() != null) {
return user.getName().toUpperCase();
} else {
return null;
}
}
// 現在的代碼
public static String test(User user) {
return Optional.ofNullable(user)
.map(user1 -> user1.getName())
.map(s -> s.toUpperCase()).orElse(null);
}
Optional總結:
filter,map或flatMap一個函數,函數的參數拿到的值一定不是null。所以我們通過filter,map 和 flatMap之類的函數可以將其安全的進行變換,最后通過orElse系列,get,isPresent 和 ifPresent將其中的值提取出來。
其實吧,用Optional類也沒有簡化很多的代碼,只是把NPE異常通過各種方法隱藏起來(包裝了一層)。通過Lambda表達式可以讓我們處理起來更加"優雅"一些。
三、最后
之前在初學的時候沒在意JDK8的特性,其實JDK更新很多時候都能給我們帶來不少好處的(簡化代碼編寫,提高性能等等),所以作為一名Java程序員,還是得多學學新特性。(話說JDK9該類又有新特性了...)
如果你要評論“醒醒吧,程序員哪來的女朋友”,“我尿黃,讓我來”之類的話,我建議你是不是好好反省一下自己,為什么別的程序員都有女朋友,就你沒有,是不是自己技術不過關了?通過“工廠”找一個有那么難嗎?再不濟也能自己new一個出來啊。
當然了,我的女朋友是現實存在的。
參考資料:
- Java 8 Optional類深度解析:https://www.cnblogs.com/xingzc/p/5778090.html
- Java8 如何正確使用 Optional:http://www.importnew.com/26066.html
- https://www.zhihu.com/question/63783295/answer/214531004
- 【Java】jdk8 Optional 的正確姿勢https://blog.csdn.net/hj7jay/article/details/52459334
如果你覺得我寫得還不錯,了解一下:
- 堅持原創的技術公眾號:Java3y。回復 1 加入Java交流群
- 文章的目錄導航(精美腦圖+海量視頻資源):https://github.com/ZhongFuCheng3y/3y