摘要:接口例子如果容器的對象存在,則對其執行調用函數得到返回值。上面一句代碼對應著最開始的老寫法方法直接看源碼方法與方法類似,區別在于函數的返回值不同。
前言
只有光頭才能變強
前兩天帶女朋友去圖書館了,隨手就給她來了一本《與孩子一起學編程》的書,于是今天就給女朋友講解一下什么是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) { MaphashMap = new HashMap<>(); hashMap.put("公眾號", "Java3y"); hashMap.put("交流群", "回復1"); // 使用增強for的方式來遍歷hashMap for (Map.Entry 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) { Listlist = new ArrayList<>(); list.add("Java3y"); list.add("3y"); list.add("光頭"); list.add("帥哥"); // 傳統的方式刪除"光頭"的元素 ListIterator 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{ void accept(T t, U u); default BiConsumer 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{ boolean test(T t); default Predicate and(Predicate super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate negate() { return (t) -> !test(t); } default Predicate or(Predicate super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static Predicate 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 Optional map(Function super T, ? extends U> mapper)這個聲明,你看懂了嗎?
// 接口 @FunctionalInterface public interface Function{ 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 staticOptional empty() { @SuppressWarnings("unchecked") Optional t = (Optional ) EMPTY; return t; } // 5、私有構造方法(帶參數),參數就是具體的要裝載的對象,如果傳進來的對象為null,拋出異常 private Optional(T value) { this.value = Objects.requireNonNull(value); } // 5.1、如果傳進來的對象為null,拋出異常 public static T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; } // 6、創建出Optional容器,并將對象(value)裝載到Optional容器中。 // 傳入的value如果為null,拋出異常(調用的是Optional(T value)方法) public static Optional of(T value) { return new Optional<>(value); } // 創建出Optional容器,并將對象(value)裝載到Optional容器中。 // 傳入的value可以為null,如果為null,返回一個沒有裝載對象的Optional對象 public static Optional 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則拋出異常 Optional2.2Optional容器簡單的方法op1 = Optional.of(user1); // 傳遞進去的對象可以為null,如果為null則返回一個沒有裝載對象的Optional容器 Optional op2 = Optional.ofNullable(user); }
// 得到容器中的對象,如果為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; Optionalop1 = 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; Optional2.3Optional容器進階用法op1 = Optional.ofNullable(user1); System.out.println(op1.isPresent()); System.out.println(op1.orElse(user)); System.out.println(op1.get()); }
當然了,我們到目前為止看起來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{ 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) { Optional2.3.2orElseGet和orElseThrow方法optional = Optional.ofNullable(user); // 如果存在user,則打印user的name optional.ifPresent((value) -> System.out.println(value.getName())); // 舊寫法 if (user != null) { System.out.println(user.getName()); } }
直接看源碼:
// 如果對象存在,則直接返回,否則返回由Supplier接口的實現用來生成默認值 public T orElseGet(Supplier extends T> other) { return value != null ? value : other.get(); } @FunctionalInterface public interface Supplier{ T get(); } // 如果存在,則返回。否則拋出supplier接口創建的異常 public 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) { Optionaloptional = 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 Optionalfilter(Predicate super T> predicate) { Objects.requireNonNull(predicate); if (!isPresent()) return this; else return predicate.test(value) ? this : empty(); } // 接口 @FunctionalInterface public interface Predicate { boolean test(T t); }
返回Optional對象我們就可以實現鏈式調用了!
例子:
public static void test(User user) { Optional2.3.4map方法optional = Optional.ofNullable(user); // 如果容器中的對象存在,并且符合過濾條件,返回裝載對象的Optional容器,否則返回一個空的Optional容器 optional.filter((value) -> "Java3y".equals(value.getName())); }
直接看源碼:
// 如果容器的對象存在,則對其執行調用mapping函數得到返回值。然后創建包含mapping返回值的Optional,否則返回空Optional。 public Optional 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{ R apply(T t); }
例子:
public static void test(User user) { Optional2.3.5flatMap方法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"; } }
直接看源碼:
// flatMap方法與map方法類似,區別在于apply函數的返回值不同。map方法的apply函數返回值是? extends U,而flatMap方法的apply函數返回值必須是Optional public Optional flatMap(Function super T, Optional> 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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72280.html
以下是Java技術棧微信公眾號發布的關于 Java 的技術干貨,從以下幾個方面匯總。 Java 基礎篇 Java 集合篇 Java 多線程篇 Java JVM篇 Java 進階篇 Java 新特性篇 Java 工具篇 Java 書籍篇 Java基礎篇 8張圖帶你輕松溫習 Java 知識 Java父類強制轉換子類原則 一張圖搞清楚 Java 異常機制 通用唯一標識碼UUID的介紹及使用 字符串...
摘要:于是抽時間看了看以后各個版本的特性,做了一個總結。年和公開版本發布,取名為。此后對應版本就是,。發布,是一個重大版本更新。在此之后,就是每六個月發布一次新版本。以上和參考資料聊了一些關于的歷史,下面我們看看各個版本有那些新特性。 【這是 ZY 第 11 篇原創技術文章】 某天在網上閑逛,突然看到有篇介紹 Java 11 新特性的文章,頓時心里一驚,畢竟我對于 Java 的版本認識...
摘要:的問題在于,版本號中編碼了它和它對之前版本的兼容性信息。但是在六個月節奏的情況下,這些信息都是未知的,在發布前任何事情都可能發生,由此規范下的版本號也會是未知的。程序會對文件的完整性做一個保護,因此修改既可能丟失。 本文是對底部參考資料的整理得到的,由于本人技術水平和英語水平都不是很高,有些詞如有翻譯錯誤或句子的理解錯誤還請指出。 JEP 286 局部變量推斷: var 傳統的 J...
摘要:它的出現是為我們解決空指針異常的,以前我們寫代碼如果不進行判斷,會經常出現異常。因為它本身就是個對象,不管放進去的對象為不為,始終不會返回,所以你也不需要在你的業務流程中進行一大堆的判斷,避免了程序運行時的空指針異常。 想必大家已經在使用jdk1.8做項目開發,但是你對于它里面的一些性特性了解多少呢?有沒有在你的項目中運用呢?現在就和我來一起梳理一下吧。 介紹 它是java.util包...
摘要:注意當多個父接口中存在相同的默認方法時,子類中以就近原則繼承。定義靜態默認方法這是版簡易計算器接口默認方法使用定義接口并提供默認打印方法定義接口默認方法支持方法形參這是數值運算基本接口。。。 總概 JAVA8 已經發布很久,而且毫無疑問,java8是自java5(2004年發布)之后的最重要的版本。其中包括語言、編譯器、庫、工具和JVM等諸多方面的新特性。 Java8 新特性列表如下:...
閱讀 3669·2021-11-23 09:51
閱讀 1661·2021-10-22 09:53
閱讀 1345·2021-10-09 09:56
閱讀 853·2019-08-30 13:47
閱讀 2155·2019-08-30 12:55
閱讀 1597·2019-08-30 12:46
閱讀 1105·2019-08-30 10:51
閱讀 2410·2019-08-29 12:43