1. 程式人生 > >JAVA 8 新特性實用總JAVA 8 新特性實用總結結

JAVA 8 新特性實用總JAVA 8 新特性實用總結結

## JAVA 8 新特性實用總結 作為一個工作兩年多的 `老` 程式猿,雖然一開始就使用 `jdk1.8` 作為學習和使用的版本,隨著技術的迭代,現有的 `JDK` 版本從兩年前到現在,已經飛速發展到了 `JDK 15` 。真的感覺有點學不動了,更新速度太快了,不過相比於現有系統以及國內趨勢。大多公司還是採用最基礎的 `1.8` 作為線上環境來使用。也是沒有任何問題的,不過我們真的 `會使用` JAVA8 嗎? > https://www.oracle.com/java/technologies/java-se-glance.html ### 新特性概述 本小結主要從 `Lambda` 表示式入手,由淺入深,按照實用性作為排行,逐步講解新特性帶給開發人員的快樂,如何更好的簡化程式碼,優化可讀性。這才是我們學習總結這一小節的一個目的。 ### 你會使用遍歷迴圈? 從最基礎的迴圈開始,迴圈無非是我們剛學習的時候就需要接觸 `for` 這個最基本的迴圈結構,而且在後面的工作總都會大量使用的一個結構,如何更好的簡化它呢? ```java // 建立測試集合 List list = Arrays.asList(1, 2, 2, 3, 4, 5, 5, 6); // 基礎迴圈 System.out.println("----------------------------1 基礎迴圈"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } // 語法糖方式 System.out.println("----------------------------2 迭代器語法糖"); for (Integer i : list) { System.out.println(i); } // lambda 表示式簡寫 System.out.println("----------------------------3 lambda"); list.forEach(item ->
System.out.println(item)); // 使用lambda 方法引用 System.out.println("----------------------------4 lambda"); list.forEach(System.out::println); ``` ```java // 以下為編譯後語法糖的程式碼 Iterator var4 = list.iterator(); while(var4.hasNext()) { Integer i = (Integer)var4.next(); System.out.println(i); } ``` 從上面的程式碼我們可以看出,隨著 `lambda` 方式的引入,程式碼變得越來越簡化,而且更加容易讀懂,寫的東西也越來越少, 1. 第一種方式則是我們常規的操作方式,一般適用於需要 `下標` 邏輯的業務中。 2. 第二種則是迭代器語法糖,對於開發者而言寫起來便捷,不過對於程式碼的編譯而言,編譯後的程式碼任是迭代器的方式,只不過語法簡單了。 3. lambda 則是一種函式式的表達方式,item 作為我們迴圈的引數,而箭頭後則是我們需要執行的程式碼塊,一句程式碼完全不必使用 `{}` 4. lambda `方法引用` 則是一種全新的方式,`引用` 二字經常被我們使用,一般在物件的引用處有表達的含義,簡而言之就是 `一個值可以從一個地方引用過來使用` ,但是現在,方法完全可以被看做一個 `值` 一樣,也可以隨意拿過來使用~ #### forEach 可能朋友們就會有疑惑,為什麼 `forEach` 的地方就可以使用 `lambda` 表示式呢,其他地方怎麼不行?我們來看看原始碼 ```java default void forEach(Consumer action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } ``` 我們發現 `Consumer` 是一個介面,內部仍然使用 `for語法糖` 形式來執行集合,呼叫了 `accept` 方法。 #### Consumer > 消費者介面,適用於入參處理,無返回值 ```java @FunctionalInterface public interface Consumer { void accept(T t); ``` 發現這個介面和其他介面唯一的不同點就是 `@FunctionalInterface` 其實這個註解就是來告訴編譯器,這個介面下的 `accept` 方法可以使用函式式寫法來描述。有了這個註解的定義,我們就可以愉快的使用函式式lambda 表示式了。 `消費者介面` 作為JDK 自帶的函式式介面,所處於 `java.util.function` 包下,並且支援鏈式操作, 接受一個指定的泛型,內部處理後,無返回值 ```java // 無返回的處理 Consumer custom = (str) -> System.out.println("first" + str); Consumer desc = custom.andThen((str) -> System.out.println("second" + str)); desc.accept("hello"); -------------------------- firsthello secondhello ``` > 稍稍總結一下lambda 的基礎語法: > > (引數)-> 一行執行程式碼 > > (引數)-> {多行執行程式碼} **單個引數完全可以省略引數的括號。** #### default > 預設實現,子類無需重寫介面定義的關鍵詞 上面的Consumer使用中,我們發現,有一個預設實現的介面,順便來說明一下 ```java default Consumer andThen(Consumer after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } ``` `default` 提供預設的實現方式,實現類無需重寫這個方法的定義,而可以直接使用。 #### 方法引用 > 把方法也可以作為值一樣來引用使用。 ```java // 使用lambda 方法引用 System.out.println("----------------------------4 lambda"); list.forEach(System.out::println); ``` 博主這裡的理解是:引用的方法需要與定義處: `default void forEach(Consumer action)` 所需要的lambda 表示式具有相同的入參個數與返回型別,才可以引用。 例如:`Consumer` 介面接受的lambda 形式為:`item -> System.out.println(item)` 而我們引用的`System.out::println` 剛好具備這樣的形式。 ```java public void println(Object x) { String s = String.valueOf(x); synchronized (this) { print(s); newLine(); } } ``` ### 優雅判空 我們都知道,JAVA 裡面最討厭的一個異常就是`NPE=NullPointerException` 空指標異常,為了避免空指標異常,我們經常不少使用`if` 作為判斷,這樣的判斷多了就容易讓人看著惱火。例如如下程式碼: ```java Person person = new Person("test", 1); if (person != null) { if (person.getName() != null) { System.out.println("123" + person.getName()); } else { // do something } } else { // do something } ``` 假設我們有一個`person` 物件,首先判斷它是否為空,如果不為空,則取值,而後再獲取 `name` 成員變數,不為空則拼接列印。這樣兩層判斷的邏輯在程式碼裡經常會見到,學習了 `Optional` 以後,我們的以上邏輯就可以修改為如下: ```java // 最佳實踐 Optional.ofNullable(person).map(p -> p.getName()).map(string -> string.concat("123")).ifPresent(System.out::println); ``` #### Function > 入參並返回一個指定型別,可以理解為轉換。 首先發現 `map` 接受一個 `Function mapper` ,具體如何使用Function ```java @FunctionalInterface public interface Function { R apply(T t); ``` ```java // 鏈式轉換 Function stringToInteger = Integer::valueOf; // andThen 將前一個處理的返回值作為後一個處理的入參 Function integerToString = stringToInteger.andThen(Integer::toHexString); String hex = integerToString.apply("123"); System.out.println(hex);// 7b ``` #### Optional > 優雅判斷空,並且執行對應操作 `Optional` 對於 `NPE` 有著很好的解決方式,可以解決我們多重if 的優化,不僅美觀,而且非常優雅。 ```java // 如果person 為null 則觸發異常 Optional.of(person); // 如果person1 為 null 則返回empty Optional.ofNullable(person1); ``` 以上是建立例項的兩種方式,一般常用第二種,第一種如果有 `null` 的情況則會觸發 `NPE` 到頭來還是沒有處理掉這個異常,所以不建議使用。 ```java private Optional() { this.value = null; } ``` ``` isPresent(): 如果不為空則返回true。 get(): 獲取當前包含的值,若是value=null 則丟擲NPE orElse(T other): 如果當前例項包含值為null,則返回other; ifPresent(Consumer consumer): 若當前例項不為空,則執行這個消費者consumer,否則返回EMPTY ``` ### Stream `stream` 作為 JAVA8 最核心的內容,融匯貫通的掌握其精髓,對開發者而言,無非是一把開啟新世界大門的鑰匙。從巨集觀的角度來講,一個語言處理最多的就是資料的集合,比如 `List` #### filter > 過濾器,過濾出你想要的集合元素。 ```java List list = Arrays.asList(1, 2, 3, 3, 4, 5, 5, 6); // 篩選偶數 long num = list.stream().filter(item -> item % 2 == 0).count(); // 3 ``` 這裡通過簡單的篩選,篩選的條件是偶數,並且最終統計它的個數。 這裡的 `filter` 接受一個 `filter(Predicate predicate)` > count 簡而言之了,就是統計前方表示式所產生的新集合個數。 #### Predicate > 斷言,也是一個函式式介面,可以使用lambda 表示式。 ```java @FunctionalInterface public interface Predicate { boolean test(T t); ``` `Predicate` 主要實現其 `test` 介面,通過邏輯執行,返回一個 `boolean` 來判斷當前元素是否可用。 ```java // 斷言字串長度大於0 Predicate stringEmpty = (str) -> str.length() > 0; Predicate startHello = (str) -> str.startsWith("hello"); System.out.println("test 空字元=" + stringEmpty.test("")); System.out.println("test hello=" + stringEmpty.test("hello")); // and 合併兩個檢驗介面,同時滿足即可 or 只要有一個滿足即可 System.out.println("test and hello world=" + stringEmpty.and(startHello).test("hello world")); System.out.println("test or world=" + stringEmpty.or(startHello).test("world")); ---------------------- test 空字元=false test hello=true test and hello world=true test or world=true ``` #### map > map 可以理解為對映,處理每個元素,並且返回任何型別。支援鏈式map, > > 上層map的返回值作為下層map的引數值。 ```java List people = Arrays.asList(new Person("hello", 1), new Person("world", 2)); // 將每一個元素的name 組裝成一個新的集合。 List names = people.stream().map(item -> item.getName()).collect(Collectors.toList()); System.out.println(names); // 多重map處理 List concat = people.stream().map(item -> item.getName()).map(name -> name.concat("-concat")).collect(Collectors.toList()); System.out.println(concat); ------------------- [hello, world] [hello-concat, world-concat] ``` map 接受一個 `map(Function mapper)` 我們上面已經討論過這個了。 #### sorted > 對元素進行排序,可以使用預設,也可以自定義排序規則。 ```java List sortedList = Arrays.asList("acc", "dee", "zdd", "wee", "abb", "ccd"); // 預設排序,字典順序,第一個字母相同,則比較第二個 List sorted = sortedList.stream().sorted().collect(Collectors.toList()); System.out.println(sorted); // 自定義實現,只比較第一個字元 List sorted2 = sortedList.stream().sorted((str1, str2) -> str1.charAt(1) - str2.charAt(1)).collect(Collectors.toList()); System.out.println(sorted2); --------------------------- [abb, acc, ccd, dee, wee, zdd] // 可以發現自定義的排序沒有比較第二個字母 [acc, abb, ccd, dee, wee, zdd] ``` 我們發現 `sorted` 接受一個 `Comparator comparator` #### Comparator > 比較器,也是函式式介面,不必多說,自然可以使用lambda ```java @FunctionalInterface public interface Comparator { int compare(T o1, T o2); ``` ```java Comparator comparator = (str1, str2) -> str1.charAt(0) - str2.charAt(0); // 自定義比較第一位字母 int a = comparator.compare("abb", "acc"); System.out.println(a); // 再次比較,如果第一個返回0,則直接返回結果,否則進行二次比較 int b = comparator.thenComparing((str1, str2) -> str1.charAt(1) - str2.charAt(1)).compare("abb", "acc"); System.out.println(b); ------------------------------ 0 -1 ``` 比較器返回一個int 值,這個int 則表示兩個元素的排列順序,按照 [ASCII表](https://tool.oschina.net/commons?type=4) 指示的值大小,如果兩個元素的差值`a-b>0` 則 a在前,b在後 #### allMatch/anyMatch > 同樣,Match 用來處理當前序列中,全部滿足、或者部分滿足,返回一個布林值 ```java List sortedList = Arrays.asList("acc", "dee", "zdd", "wee", "abb", "ccd"); // 所有的元素都斷言通過,就返回true,否則false boolean startWithA = sortedList.stream().allMatch(str -> str.startsWith("a")); System.out.println(startWithA); // 只要有一個滿足就返回true boolean hasA = sortedList.stream().anyMatch(str -> str.startsWith("a")); System.out.println(hasA); ------------------------ false true ``` 以上就是 `stream` 常用的一些總結,總結了一些非常常用的,未總結到的內容下期補充。 ### 其他 這裡提一下區域性變數final 語義。 #### 自定義函式式介面 模仿以上的任意一個函式介面,我們可以寫出這樣的一個轉換介面,將指定型別轉換為指定型別 ```java @FunctionalInterface public interface FunctionInterface { R cover(A t); } ``` 通過自定義函式介面,我們可以寫出如下程式碼,來進行轉換,不過涉及到一些引數的改變。 ```java // num 區域性變數如果在lambda 中使用,則隱式含有final 語義 final int num = 1; FunctionInterface function4 = (val) -> Integer.valueOf(val + num); Integer result4 = function4.cover("12"); // num = 2; // 這裡不能改變,修改則不能通過編譯 ``` **以上內容均為博主自我學習中常用的一些總結,難免涉及不全面,還請包涵!** ### Git https://gitee.com/mrc1999/java-guide ### 參考內容 https://snailclimb.gitee.io/javaguide/#/ ### 歡迎關注 ![歡迎關注微信公眾號](https://file.chaobei.xyz/blogs/banner_1591192617