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