我們一起學(xué)習(xí) WebFlux 前置知識
最近太忙了,發(fā)文頻率有點不穩(wěn)定,理解萬歲。前面和大家說了要更 WebFlux,學(xué)習(xí) WebFlux 之前,我們先來學(xué)習(xí)一些前置知識。
Rome was not built in a day。
WebFlux 也不是一幫人拍腦門突然發(fā)明的,它是一個漫長的過程,WebFlux 本身在逐步完善,各種配套工具/理論也在逐步發(fā)展。
因此當(dāng)松哥想寫 WebFlux 的時候,發(fā)現(xiàn)沒法直接從 WebFlux 本身開始寫起,對于很多沒有接觸過函數(shù)式編程的人來說,上來就整 WebFlux 還是有一些挑戰(zhàn)的,想來想去,我覺得還是先來和大家捋一捋 JDK8 中的一些舊玩意。
雖然 JDK8 發(fā)布距今已經(jīng)七八年了,但是相信還是有相當(dāng)多小伙伴用著 JDK8,寫著 JDK6 的代碼。所以我們有必要回顧一下 JDK8,也算是我們學(xué)習(xí) WebFlux 的一些前置知識。
好啦,開整吧。
1.Lambda 表達(dá)式的四種寫法
JDK8 中引入了 Lambda,這個大家都知道,雖然現(xiàn)在 JDK 都出到 16 了,但是老實說,項目中的 Lambda 表達(dá)式似乎還是很少有人用。有的團(tuán)隊技術(shù)風(fēng)格激進(jìn),可能會見到很多 Lambda,但是大部分技術(shù)團(tuán)隊還是比較保守的。今天為了學(xué)習(xí) WebFlux,我們還是先來回顧一下 Lambda 表達(dá)式的幾種寫法。
先來說說,如果要用 Lambda,必須是只有一個需要強制實現(xiàn)方法的接口,我們可以使用 @FunctionalInterface 注解去標(biāo)記該接口:
- @FunctionalInterface
- interface ICalculator{
- int square(int i);
- }
此時如果該接口中有多個空方法,編譯期間就會報錯。
現(xiàn)在我們建議盡量將一個接口設(shè)計的小一些,這樣也滿足單一職責(zé)原則。
不過 JDK8 中引入了 default 方法,就是自帶默認(rèn)實現(xiàn)的那種,自帶默認(rèn)實現(xiàn)的方法可以有多個,這個并不影響 Lambda,并且 @FunctionalInterface 注解也不會去檢查默認(rèn)方法的數(shù)量。
1.1 單個參數(shù)的
如果只是一個參數(shù),那么直接寫參數(shù)即可,例如如下代碼:
- interface ICalculator{
- int square(int i);
- }
- public class LambdaDemo01 {
- public static void main(String[] args) {
- ICalculator ic = i -> i * i;
- int square = ic.square(5);
- System.out.println("square = " + square);
- }
- }
當(dāng)函數(shù)只有一個參數(shù)的時候,直接寫即可,不需要添加 ()。
1.2 多個參數(shù)
多個參數(shù)的話,就需要寫上 () 了,以 Spring Security 中登錄成功的回調(diào)為例(不了解 Spring Security 的小伙伴可在公號后臺回復(fù) ss):
- .defaultLogoutSuccessHandlerFor((req,resp,auth)->{
- resp.setContentType("application/json;charset=utf-8");
- Map<String, Object> result = new HashMap<>();
- result.put("status", 200);
- result.put("msg", "使用 logout1 注銷成功!");
- ObjectMapper om = new ObjectMapper();
- String s = om.writeValueAsString(result);
- resp.getWriter().write(s);
- },new AntPathRequestMatcher("/logout1","GET"))
- .defaultLogoutSuccessHandlerFor((req,resp,auth)->{
- resp.setContentType("application/json;charset=utf-8");
- Map<String, Object> result = new HashMap<>();
- result.put("status", 200);
- result.put("msg", "使用 logout2 注銷成功!");
- ObjectMapper om = new ObjectMapper();
- String s = om.writeValueAsString(result);
- resp.getWriter().write(s);
- },new AntPathRequestMatcher("/logout2","POST"))
- .and()
- .csrf().disable();
這種情況,方法有多個參數(shù),此時使用 Lambda 表達(dá)式就需要加上 ()。
1.3 要寫參數(shù)類型的
正常來說用 Lambda 時候不需要寫上參數(shù)類型,但是如果你需要寫,就要加上 (),還是上面那個例子,如下:
- interface ICalculator{
- int square(int i);
- }
- public class LambdaDemo01 {
- public static void main(String[] args) {
- ICalculator ic = (int i) -> i * i;
- int square = ic.square(5);
- System.out.println("square = " + square);
- }
- }
1.4 方法體不止一行的
如果方法體不止一行,需要用上 {},如果方法體只有一行,則不需要 {},參考上面 2、3。
2.函數(shù)接口
JDK8 中自帶了函數(shù)式接口,使用起來也非常方便。
2.1基本應(yīng)用
我們先來看一個簡單的例子。
假設(shè)我有一個打招呼的接口 SayHello,SayHello 接口中只有一個 sayHello 方法,然后在 User 類中調(diào)用該接口對應(yīng)的方法,最終用法如下:
- @FunctionalInterface
- interface SayHello {
- String sayHello(String name);
- }
- class User {
- private String username;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String say(SayHello sayHello) {
- return sayHello.sayHello(this.username);
- }
- }
- public class LambdaDemo02 {
- public static void main(String[] args) {
- User user = new User();
- user.setUsername("javaboy");
- String say = user.say((username) -> "hello " + username);
- System.out.println("say = " + say);
- }
- }
分析 main 方法中的調(diào)用過程之后,我們發(fā)現(xiàn),在調(diào)用時最核心的是如下一行代碼:
- (username) -> "hello " + username
在這段代碼中,我們只關(guān)心方法的輸入和輸出,其他的都不是我所考慮的,為了一個簡單的輸入輸出,我還要額外定義一個接口,這顯然不太劃算。
JDK8 中提供了函數(shù)接口,可以幫助我們簡化上面的接口定義。如下:
- class User2 {
- private String username;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String say(Function<String,String> sayHello) {
- return sayHello.apply(this.username);
- }
- }
- public class LambdaDemo03 {
- public static void main(String[] args) {
- User2 user2 = new User2();
- user2.setUsername("javaboy");
- String say = user2.say((username) -> "hello " + username);
- System.out.println("say = " + say);
- }
- }
可以用 Function<String,String> 代替我們前面的接口定義,這里有兩個泛型,第一個泛型表示接口輸入的參數(shù)類型,第二個泛型表示接口輸出的參數(shù)類型,而且大家注意,我們最終 main 方法中的調(diào)用方式是不變的。有了 Function 函數(shù)之后,以后我們就不需要定義一些簡單的接口了。
而且 Function 函數(shù)還支持鏈?zhǔn)讲僮鳎缦拢?/p>
- public class LambdaDemo03 {
- public static void main(String[] args) {
- User2 user2 = new User2();
- user2.setUsername("javaboy");
- Function<String, String> func = (username) -> "hello " + username;
- String say = user2.say(func.andThen(s -> "你好 " + s));
- System.out.println("say = " + say);
- }
- }
2.2 其他函數(shù)接口
- 接口 輸入?yún)?shù) 返回類型 說明
- UnaryOperator T T 一元函數(shù),輸入輸出類型相同
- Predicate T boolean 斷言
- Consumer T / 消費一個數(shù)據(jù),只有輸入沒有輸出
- Function<T,R> T R 輸入 T 返回 R,有輸入也有輸出
- Supplier / T 提供一個數(shù)據(jù),沒有輸入只有輸出
- BiFunction<T,U,R> (T,U) R 兩個輸入?yún)?shù)
- BiPredicate<L, R> (L,R) boolean 兩個輸入?yún)?shù)
- BiConsumer<T, U> (T,U) void 兩個輸入?yún)?shù)
- BinaryOperator (T,T) T 二元函數(shù),輸入輸出類型相同
接下來我們來看看這些函數(shù)接口。
2.2.1 UnaryOperator
當(dāng)輸入輸出類型相同時,可以使用 UnaryOperator 函數(shù)接口,例如我們上面的代碼,修改之后如下:
- class User2 {
- private String username;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String say(UnaryOperator<String> sayHello) {
- return sayHello.apply(this.username);
- }
- }
- public class LambdaDemo03 {
- public static void main(String[] args) {
- User2 user2 = new User2();
- user2.setUsername("javaboy");
- UnaryOperator<String> func = (username) -> "helloo " + username;
- String say = user2.say(func);
- System.out.println("say = " + say);
- }
- }
2.2.2 Predicate
Predicate 輸入一個 T 類型的參數(shù),輸出一個 boolean 類型的值。
舉一個簡單的例子,例如如下代碼,我們定義一個 List 集合中存放著用戶姓名,現(xiàn)在要過濾出所有姓張的用戶,代碼如下:
- public class LambdaDemo04 {
- public static void main(String[] args) {
- List<String> names = Arrays.asList("張三", "里斯", "張五");
- List<String> list = names.stream().filter(s -> s.startsWith("張")).collect(Collectors.toList());
- for (String s : list) {
- System.out.println("s = " + s);
- }
- }
- }
filter 中傳入的就是一個 Predicate 函數(shù)接口,這個接口接收 String 類型的數(shù)據(jù),返回一個 boolean。
注意
一些常用類型的函數(shù)接口,JDK 中直接提供了相關(guān)的類供我們使用,例如 Predicate
2.2.3 Consumer
看名字就知道,這個是消費數(shù)據(jù),只有輸入沒有輸出。
例如集合的遍歷就可以使用 Consumer 函數(shù)接口。
- public class LambdaDemo04 {
- public static void main(String[] args) {
- List<String> names = Arrays.asList("張三", "里斯", "張五");
- names.stream().forEach(s -> System.out.println(s));
- }
- }
2.2.4 Supplier
Supplier 剛好和 Consumer 相反,它只有輸出沒有輸入。有的時候我們的工廠方法沒有輸入只有輸出,這個時候就可以考慮使用 Supplier(如果有輸入?yún)?shù),則可以考慮使用 Function 函數(shù)接口)。
- Supplier<Connection> supplier = ()->{
- Connection con = null;
- try {
- con = DriverManager.getConnection("", "", "");
- } catch (SQLException e) {
- e.printStackTrace();
- }
- return con;
- };
- Connection connection = supplier.get();
3.小結(jié)
其實 WebFlux 前置知識還是蠻多的,今天先聊這些吧,我們后面繼續(xù)。
本文轉(zhuǎn)載自微信公眾號「江南一點雨」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系江南一點雨公眾號。