我的名字叫阿超 年齡?21?歲 家在灶王鎮西南部的別墅區區內 未婚 我在一家普通公司做?Java?開發 每天最晚也會在八點前回家 不抽煙 偶爾沾點酒 晚上十二點上床 保證睡足八個小時 睡前寫一篇博客 再做二十分鐘仰臥起坐暖身 然后再睡覺 基本能熟睡到天亮 像嬰兒一樣不留下任何疲勞和壓力 就這樣迎來第二天的早晨 醫生都說我很正常。
阿超是新晉?Dromara?開源組織成員、Hutool Committer
目前技術棧包括不限于?Java、JavaScript、TypeScript、React、Vue、Python?等
熱愛做開源,給?Sa-Token、Easy-ES?等項目都貢獻過代碼
正文
距離我加入?hutool-commiter?已經有一段時間了,想起曾經封裝過的一個類?Opt,就是使用?lambda?解決空指針問題。按照慣例,先介紹下?dromara?組織下的項目?hutool
Hutool 是一個小而全的 Java 工具類庫,通過靜態方法封裝,降低相關 API 的學習成本,提高工作效率,使 Java 擁有函數式語言般的優雅,讓 Java 語言也可以 “甜甜的”。
這個類?Opt?的靈感來源是對?jdk?內置的?java.util.Optional?的拓展,在一些細節方面進行了簡化處理????
// 之前:
String username;
if (user != null && user.getUsername() != null) {
?username = user.getUsername();
} else {
?username = "no name";
}
// 現在:
String username = Opt.ofNullable(user).map(User::getUsername).orElse("no name");
關于?Opt?和?Optional?的差異我們先按下不表
下面主要是通過其介紹?lambda?的使用
快速上手
依靠?idea?編譯器的提示進行快速上手
下方是判斷?user?是否為空,不為空通過?User#getSchool()?獲取學校名的操作
例如此處我寫到這里
User user = new User();
// idea提示下方參數,果沒顯示,光標放到括號里按ctrl+p主動呼出 ? ? ? ? ? ?
? ? ? ? |Function<? super User,?> mapper|
Opt.ofNullable(user).map()
這里?idea?為我們提示了參數類型,可這個?Function?我也不知道它是個什么
實際上,我們?new?它一個就好了
Opt.ofNullable(user).map(new Fun)
? ? ? ? ? ? ? ? ? ? ? ? ? ?|Function<User, Object>{...} (java.util.function) ? | ?<-戳我
? ? ? ? ? ? ? ? ? ? ? ? ? ?|Func<P,R> cn.hutool.core.lang.func ? ? ? ? ? ? ? ? |
這里?idea?提示了剩下的代碼,我們選?Function?就行了,接下來如下:
Opt.ofNullable(user).map(new Function<User, Object>() {
})
此處開始編譯報錯了,不要著急,我們這里根據具體操作選取返回值
例如這里是想判斷?user?是否為空,不為空時調用?getSchool,從而獲取其中的返回值?String?類型的?school
我們就如下寫法,將第二個泛型,也就是象征返回值的泛型改為?String:
Opt.ofNullable(user).map(new Function<User, String>() {
})
然后這里紅線提示了,我們就使用?idea?的修復所有,默認快捷鍵?alt+ 回車???????
Opt.ofNullable(user).map(new Function<User, String>() {
}) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| 💡 Implement methods ? ? ? ? ? ? ? ? ?| ?<-選我
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| ? ?Introduce local variable ? ? ? ? ?|
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| ? ?Rollback changes in current line ? |
選擇第一個?Implement methods?即可,這時候彈出一個框,提示讓你選擇你想要實現的方法
這里就選擇我們的?apply?方法吧,按下一個回車就可以了,或者點擊選中?apply,再按一下?OK?按鈕???????
||IJ| Select Methods to Implement ? ? ? ? ? ? ? ? ? ? ? ?X |
| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
| 👇 ?? ?| ?? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
| -------------------------------------------------------- |
| | java.util.function.Function ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
| | ? 🔓 apply(t:T):R ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ?<-選我選我
| | ? 🔓 compose(before:Function<? super V,? extents T):Fu|
| | ? 🔓 andThen(after:Function<? super R,? extends V>):Fu|
| | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
| | ======================================== ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
| -------------------------------------------------------- |
| ?? Copy JavaDoc ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
| ?? Insert @Override ? ? ? ? ? ? ? | ?OK ?| ?| CANCEL | ? | ? ? <-選完點我點我
此時此刻,代碼變成了這樣子???????
Opt.ofNullable(user).map(new Function<User, String>() {
? ?@Override
? ?public String apply(User user) {
? ? ? ?return null;
? ?}
})
這里重寫的方法里面就寫你自己的邏輯 (別忘了補全后面的分號)????
Opt.ofNullable(user).map(new Function<User, String>() {
? ?@Override
? ?public String apply(User user) {
? ? ? ?return user.getSchool();
? ?}
});
我們可以看到,上邊的?new Function()?變成了灰色
我們在它上面按一下?alt+enter(回車)???????
Opt.ofNullable(user).map(new Function<User, String>() {
? ?@Override ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| 💡 Replace with lambda ? ? ? ? ? ? > | ?<-選我啦
? ?public String apply(User user) { ? ? ? | 💡 Replace with method reference ? > |
? ? ? ?return user.getSchool(); ? ? ? ? ? | 💎 balabala... ? ? ? ? ? ? ? ? ? ? > |
? ?}
});
選擇第一個?Replace with lambda,就會自動縮寫為?lambda?啦
Opt.ofNullable(user).map(user1 -> user1.getSchool());
如果選擇第二個,則會縮寫為我們雙冒號格式
Opt.ofNullable(user).map(User::getSchool);
接下來我們獲取值即可???????
// 這里的school可能為null,
String school = Opt.ofNullable(user).map(User::getSchool).get();
// 如果想要為其提供默認值,可以使用orElse
String school = Opt.ofNullable(user).map(User::getSchool).orElse("NO_SCHOOL");
// 如果想要為null時才調用方法為其提供默認值,可以使用orElseGet
// 這在一些為null時 使用db新增并返回值的場景下很有用
String school = Opt.ofNullable(user).map(User::getSchool).orElseGet(() -> User.getDefaultSchool());
進階
簡單來說:函數式編程就是把我們的函數 (方法) 作為參數 / 變量傳遞、調用等,有點像反射的?Method?對象,都是作為函數的載體
例子:自定義函數式接口???????
import java.io.Serializable;
/**
* 可序列化的Functional
*
* @author VampireAchao
* @since 2021/6/13 16:42
*/
@FunctionalInterface
public interface Func<T, R> extends Serializable {
? ?/**
? ? * 調用
? ? *
? ? * @param t 參數
? ? * @return 返回值
? ? */
? ?R apply(T t);
}
我們定義一個類可以去實現該接口??????????????
/**
* 可序列化的函數式接口實現類
*
* @author VampireAchao
* @since 2021/6/13 16:45
*/
public class FuncImpl implements Func<Object, String> {
? ?/**
? ? * 調用
? ? *
? ? * @param o 參數
? ? * @return 返回值
? ? */
? ?@Override
? ?public String apply(Object o) {
? ? ? ?return o.toString();
? ?}
}
這里就有個問題:假設我有很多的地方需要不同的類去實現?Func,我就得每次都去寫這么一個類,然后實現該接口并重寫方法
這樣很麻煩!因此我們使用匿名內部類???????
? ? ? ?Func<String, Integer> func = new Func<String, Integer>() {
? ? ? ? ? ?/**
? ? ? ? ? ? * 調用
? ? ? ? ? ? *
? ? ? ? ? ? * @param s 參數
? ? ? ? ? ? * @return 返回值
? ? ? ? ? ? */
? ? ? ? ? ?@Override
? ? ? ? ? ?public Integer apply(String s) {
? ? ? ? ? ? ? ?return s.hashCode();
? ? ? ? ? ?}
? ? ? ?};
我們可以看到,使用了匿名內部類后不用每次去新建這個類了,只需要在調用的地方,new?一下接口,創建一個匿名內部類即可
但這樣還有個問題,我們每次都要寫這么一大堆,特別麻煩
由此而生,我們有了?lambda?這種簡寫的形式???????
Func<String, String> func1 = (String s) -> {
? ?return s.toUpperCase();
};
如果只有一行,我們可以省略掉中括號以及?return
Func<String, String> func2 = (String s) -> s.toUpperCase();
我們可以省略掉后邊?lambda?中的參數類型,因為前面已經定義過了,編譯器能自動推斷
? ? ? ?Func<String, String> func3 = s -> s.toUpperCase();
如果我們滿足特定的形式,我們還可以使用雙冒號的形式縮寫
Func<String, String> func4 = String::toUpperCase;
這里除了我們的參數 -> 返回值寫法:
s-> s.toUpperCase()
還有例如無參數帶返回值寫法?()->"yes"、無參無返回值寫法?()->{}?等等
而雙冒號這種寫法至少有如下幾種:??????
package com.ruben;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
/**
* 語法糖——雙冒號寫法::
*
* @author VampireAchao
* @since 2021/7/1 17:44
*/
public class MethodReferences {
? ?public static Object staticSupplier() {
? ? ? ?return "whatever";
? ?}
? ?public Object instanceSupplier() {
? ? ? ?return "whatever";
? ?}
? ?public Object anonymousInstanceFunction() {
? ? ? ?return "whatever";
? ?}
? ?public static void main(String[] args) {
? ? ? ?// 引用構造函數
? ? ? ?Supplier<MethodReferences> conSup = () -> new MethodReferences();
? ? ? ?conSup = MethodReferences::new;
? ? ? ?// 數組構造函數引用
? ? ? ?IntFunction<int[]> intFunction = value -> new int[value];
? ? ? ?// intFunc == new int[20];
? ? ? ?int[] intFuncResult = intFunction.apply(20);
? ? ? ?// 引用靜態方法
? ? ? ?Supplier<Object> statSup = () -> staticSupplier();
? ? ? ?statSup = MethodReferences::staticSupplier;
? ? ? ?Object statSupResult = statSup.get();
? ? ? ?// 引用特定對象的實例方法
? ? ? ?Supplier<Object> instSup = new MethodReferences()::instanceSupplier;
? ? ? ?instSup = new MethodReferences()::instanceSupplier;
? ? ? ?Object instSupResult = instSup.get();
? ? ? ?// 引用特定類型的任意對象的實例方法
? ? ? ?Function<MethodReferences, Object> anonInstFunc = streamDemo -> streamDemo.anonymousInstanceFunction();
? ? ? ?anonInstFunc = MethodReferences::anonymousInstanceFunction;
? ?}
}
順便放幾個常用的函數式接口寫法???????
package com.ruben;
import java.math.BigDecimal;
import java.util.function.*;
/**
* 常用的幾個函數式接口寫法
*
* @author VampireAchao
* @since 2021/7/1 17:44
*/
class Usual {
? ?public static Consumer<Object> consumer() {
? ? ? ?// 有參數無返回值
? ? ? ?return o -> {
? ? ? ?};
? ?}
? ?public static Function<Integer, Object> function() {
? ? ? ?// 有參數有返回值
? ? ? ?return o -> o;
? ?}
? ?public static Predicate<Object> predicate() {
? ? ? ?// 有參數,返回值為boolean,注意 o -> null 寫法調用test執行lambda時會NPE
? ? ? ?return o -> true;
? ?}
? ?public static Supplier<Object> supplier() {
? ? ? ?// 無參數有返回值
? ? ? ?return Object::new;
? ?}
? ?public static BiConsumer<String, Integer> biConsumer() {
? ? ? ?// 倆參數無返回值
? ? ? ?return (q, o) -> {
? ? ? ?};
? ?}
? ?public static BiFunction<Integer, Long, BigDecimal> biFunction() {
? ? ? ?// 倆參數,有返回值
? ? ? ?return (q, o) -> new BigDecimal(q).add(BigDecimal.valueOf(o));
? ?}
? ?public static UnaryOperator<Object> unaryOperator() {
? ? ? ?// 一個參數,返回值類型和參數一樣
? ? ? ?return q -> q;
? ?}
? ?public static BinaryOperator<Object> binaryOperator() {
? ? ? ?// 倆參數和返回值類型保持一致
? ? ? ?return (a, o) -> a;
? ?}
}
?
Stream 介紹
Java 8 API 添加了一個新的抽象稱為流 Stream,可以讓你以一種聲明的方式處理數據。方法全是傳入函數作為參數,來達到我們的目的。???????
// 聲明式編程是告訴計算機需要計算“什么”而不是“如何”去計算
// 現在,我想要一個List,包含3個數字8
List<Integer> list =
? ? ? ?// 我想要:
? ? ? ?Stream
? ? ? ?// 8
? ? ? ?.generate(() -> 8)
? ? ? ?// 3個
? ? ? ?.limit(3)
? ? ? ?// 最后收集起來轉為List
? ? ? ?.collect(Collectors.toList());
// 結果 [8, 8, 8]
Stream?使用一種類似用?SQL?語句從數據庫查詢數據的直觀方式來提供一種對?Java?集合運算和表達的高階抽象。???????
// 就像sql里的排序、截取
// 我要把傳入的list逆序,然后從第五個(元素下標為4)開始取值,取4條
List<String> list = Arrays.asList("dromara", "Hmily", "Hutool", "Sa-Token", "Jpom", "TLog", "Cubic", "Koalas-rpc", "Fast Request");
list = list.stream()
? ? ? ?// 排序(按照自然順序的逆序)
? ? ? ?.sorted(Comparator.reverseOrder())
? ? ? ?// 從下標為4開始取值
? ? ? ?.skip(4)
? ? ? ?// 取4條
? ? ? ?.limit(4)
? ? ? ?// 最后收集起來轉為List
? ? ? ?.collect(Collectors.toList());
// 結果 [Jpom, Hutool, Hmily, Fast Request]
Stream API?可以極大提高?Java?程序員的生產力,讓程序員寫出高效率、干凈、簡潔的代碼???????
/**
* 老辦法實現一個list,存儲3個8,并轉換為String
*
* @return [8, 8, 8]
*/
private static List<String> oldFunc() {
? ?List<Integer> list = Arrays.asList(8, 8, 8);
? ?List<String> stringList = new ArrayList<>(list.size());
? ?for (Integer integer : list) {
? ? ? ?stringList.add(String.valueOf(integer));
? ?}
? ?return stringList;
}
/**
* Stream實現一個list,存儲3個8,并轉換為String
*
* @return [8, 8, 8]
*/
private static List<String> newFunc() {
? ?return Stream.generate(() -> 8).limit(3).map(String::valueOf).collect(Collectors.toList());
}
生成?26?個大寫字母組成的集合???????
List<Character> abc = Stream.iterate('A', i -> (char) (i + 1)).limit(26).collect(Collectors.toList());
// [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。???????
// 管道中傳輸,節點中處理
int pipe = abc.stream()
? ? ? ?// 篩選大于G的字母
? ? ? ?.filter(i -> i > 'G')
? ? ? ?// 排序 按照自然排序順序逆序
? ? ? ?.sorted(Comparator.reverseOrder())
? ? ? ?.mapToInt(Object::hashCode)
? ? ? ?// 聚合求和
? ? ? ?.sum();
// 1539
元素流在管道中經過中間操作(intermediate operation)的處理,最后由最終操作?(terminal operation)?得到前面處理的結果。???????
// 將26個大寫字母Character集合轉換為String然后轉換為小寫字符
List<String> terminalOperation = abc.stream()
? ? ? ?// 中間操作(intermediate operation)
? ? ? ?.map(String::valueOf).map(String::toLowerCase)
? ? ? ?// 最終操作
? ? ? ?.collect(Collectors.toList());
// [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]
?
EasyStream
這是即將推出的?hutool-6.x?新特新之一,對?Stream?進行了進一步簡化 (設計靈感來源新版?jdk?新特性以及日常使用痛點),例如:???????
// 使用toList代替collect(Collectors.toList())
List<String> toList = EasyStream.of(list).map(String::valueOf).toList();
// 使用toMap代替collect(Collectors.toMap(String::valueOf, Function.identity()))
Map<String, Integer> identityMap = EasyStream.of(list).toMap(String::valueOf);
// 可以通過三參數創建無限流,提供終止條件
List<Integer> list = EasyStream.iterate(0, i -> i < 3, i -> i + 1).toList();
// 提供遍歷下標
List<String> list = Arrays.asList("dromara", "hutool", "sweet");
List<String> mapIndex = EasyStream.of(list).mapIdx((e, i) -> i + 1 + "." + e).toList();
// 結果為 ["1.dromara", "2.hutool", "3.sweet"]
// ...
當然這個類還在完善中,目前只是一個實驗性功能,還存在一些爭議和問題,后續優化完成后會開放使用
EasyStream?源碼二維碼,大家別忘了點個?star
也歡迎大家來到知識星球和我互動
???????
|