Jdk8提供的函數式介面都在java.util.function包下,Jdk8的函數式型別的介面都有@FunctionInterface註解所標註,但實際上即使沒有該註解標註的有且只有一個抽象方法的介面,都可以算是函數式介面。
在JDK8中內建的四大核心函數式介面如下:
介面型別 | 引數型別 | 返回型別 | 作用 | Stream流中的應用場景 | |
---|---|---|---|---|---|
Consumer<T> | 消費型介面 | T | void | 對型別為T的物件進行操作,包含方法為accpet(T t) | 如forEach、peek等方法的函數式介面都是Consumer型別 |
Supplier<T> | 供給型介面 | 無 | T | 返回型別為T的物件,包含方法為T get() | 如collect等方法的某些方法過載就是用的Supplier型別 |
Function<T,R> | 函數型介面 | T | R | 對型別為T的物件進行操作,返回結果為 R型別的物件,包含方法為R apply(T t) | 如map,flatMap等方法的函數式介面都是Function型別 |
Predicate<T> | 斷言型介面 | T | boolean | 確定型別為T的物件是否滿足約束,並返回約束結果,包含方法為boolean test(T t) |
Consumer<T>消費型介面,顧名思義就是消費並處理引數,且不反饋呼叫環境
public class Main { /** * Consumer<T> * 消費型介面:顧名思義主要用於消費引數,不反饋呼叫環境(沒有返回值) * accept: 抽象方法實現,用於呼叫方法。 * andThen: 預設實現方法,內部允許我們鏈式呼叫 */ public static void main(String[] args) { // 給定字串轉為大寫並輸出到控制檯,匿名內部類的方式實現 Consumer<String> con1 = new Consumer<String>() { @Override public void accept(String str) { System.out.println("通過匿名內部類的方式:"+ str.toUpperCase()); } }; // 執行該方法的時候,我們傳入了給定引數字串,它會去執行我們上述實現的accept方法並傳入引數,最後執行我們給定的程式碼邏輯 con1.accept("abc"); // 給定字串轉為大寫並輸出到控制檯,通過Lambda表示式實現 Consumer<String> con2 = (text)-> System.out.println("通過Lambda表示式的方式:"+ text.toUpperCase()); con2.accept("goods"); /** * 最終結果: * 通過匿名內部類的方式:ABC * 通過Lambda表示式的方式:GOOD * 使用lambda表示式,我們只需要記住參數列和執行邏輯即可,其他的我們無需關注。 */ }}
public class Main { /** * Consumer<T> * 消費型介面:顧名思義主要用於消費引數,不反饋呼叫環境(沒有返回值) * accept: 抽象方法實現,用於呼叫方法。 * andThen: 預設實現方法,內部允許我們鏈式呼叫 */ public static void main(String[] args) { // 1.我們需要將集合進行排序後在輸出到控制檯 Consumer<List> con1 = list-> { System.out.println("排序前的集合:"+ list); Collections.sort(list); System.out.println("排序後的集合:"+list); }; con1.accept(Arrays.asList(1,5,3,2,9,6,7)); /** * 最終結果: * 排序前的集合:[1, 5, 3, 2, 9, 6, 7] * 排序後的集合:[1, 2, 3, 5, 6, 7, 9] */ // 上面執行邏輯實現分兩步,第一步需要獲取到給定集合進行排序,第二個則是輸出排序後的集合 // 如果以上兩個步驟分別用兩個consumer也可以實現,我們可以定義一個方法接收兩個consumer進行操作 accept(Arrays.asList(1,5,3,2,9,6,7),list-> { System.out.println("andThen鏈式呼叫前集合:"+list); Collections.sort(list); },list-> System.out.println("andThen鏈式呼叫後集合:"+ list)); /** * 最終結果: * andThen鏈式呼叫前集合:[1, 5, 3, 2, 9, 6, 7] * andThen鏈式呼叫後集合:[1, 2, 3, 5, 6, 7, 9] */ // 如果consumer引數多個的話,我們可以直接在Lambda表示式進行鏈式呼叫,不費那勁定義方法了 Consumer<List> con2 = ((Consumer<List>) list -> { System.out.println("lambda表示式的鏈式呼叫前集合:" + list); Collections.sort(list); }).andThen(list -> System.out.println("lambda表示式的鏈式呼叫後集合:"+list)); // 需要注意的是:要使用這種方式,第一個consumer要進行鏈式呼叫必須要強行指定為(Consumer)型別,後續的介面才能夠呼叫方法 con2.accept(Arrays.asList(1,53,31,25,99,62,17)); /** * 最終結果: * lambda表示式的鏈式呼叫前集合:[1, 53, 31, 25, 99, 62, 17] * lambda表示式的鏈式呼叫後集合:[1, 17, 25, 31, 53, 62, 99] */ } public static void accept(List<Integer> list,Consumer<List> con1,Consumer<List> con2){ // 鏈式呼叫時會優先執行左邊的介面實現,依次往右執行 我們的需求是先排序後輸出,第一個Consumer是排序,第二個是輸出。 con1.andThen(con2).accept(list); } }
1.函數式介面的本質實際上就是將函數以引數的形式進行傳遞
2.Consumer是消費型的函數式介面,通常用於資料內部處理,沒有返回值
3.除了Consumer之外,還有各種消費型的函數式介面,還有IntConsumer、LongConsumer等、如果需要傳遞兩個引數則可以使用BIFunction、也可以根據自身需求進行自定義。
Consumer<T>供給型函數式介面,顧名思義就是供給資料給呼叫環境,不接收引數傳遞
public class Main{ /** * 供給型函數式介面顧名思義就是顧名思義就是供給資料給呼叫環境,不接收引數傳遞 * T get() : 返回泛型T型別的引數到呼叫環境 */ public static void main(String[] args) { // 返回一個0-100間的亂數 Supplier<Integer> sup1 = new Supplier<Integer>() { @Override public Integer get() { int res = new Random().nextInt(100); System.out.println("通過匿名內部類的方式獲取到的亂數:"+ res); return res; } }; // 執行該方法的時候,它會去執行我們上述實現的get方法。 sup1.get(); // 通過lambda表示式的方式進行實現 Supplier<Integer> sup2 = ()-> { int res = new Random().nextInt(100); System.out.println("通過lambda表示式的方式獲取到的亂數:"+ res); return res;}; sup2.get(); /** * 最終結果: * 通過匿名內部類的方式獲取到的亂數:28 * 通過lambda表示式的方式獲取到的亂數:62 * 使用lambda表示式,我們只需要記住參數列和執行邏輯即可,其他的我們無需關注。 */ } }
public class Main{ public static Map<String,String> redis = new HashMap(); /** * 供給型函數式介面顧名思義就是顧名思義就是供給資料給呼叫環境,不接收引數傳遞 * T get() : 返回泛型T型別的引數到呼叫環境 */ public static void main(String[] args) { // 1.(模擬)查詢某個Key在redis中有沒有快取,快取沒有則從資料庫取完存入redis再返回,有的話則直接返回 String val = getCache("title"); String val2 = getCache("title"); String val3 = getCache("title"); /** * 最終結果: 從資料庫中獲取:我是標題 從快取中獲取:我是標題 從快取中獲取:我是標題 */ // 可以看到經過第一次後續都是直接從快取中取出的資料 } public static String getCache(String key){ String val = redis.get(key); if(Objects.isNull(val)){ // 獲取資料庫的資料 val = getDbVal(() -> "我是標題"); System.out.println("從資料庫中獲取:"+val); redis.put(key,val); return val; } System.out.println("從快取中獲取:"+val); return val; } public static String getDbVal(Supplier<String> supplier){ return supplier.get(); } }
1.函數式介面的本質實際上就是將函數以引數的形式進行傳遞
2.Supplier是供給型的函數式介面,通常用於構建某個物件處理後返回撥用環境
3.除了Supplier之外,還有各種供給型的函數式介面,還有BooleanSupplier、IntSupplier等。
Function<T,R>函數型的函數式介面,泛型T是引數、泛型R則是返回值、主要應用場景做資料型別轉換等。
public class Main{ /** * Function<T,R>函數型的函數式介面,泛型T是引數、泛型R則是返回值、主要應用場景做資料型別轉換等。 * R apply(T t): 抽象方法實現,用於呼叫方法並返回泛型R * <V> Function<T, V> andThen: 預設實現方法,內部允許我們鏈式呼叫,與其他的andThen原理一致。 * <V> Function<V, R> compose: 預設實現方法,內部允許我們鏈式呼叫,呼叫方式與andThen一樣,但執行順序不一樣,compose是先執行compose中的函數介面,再執行左邊呼叫的函數介面,依次往左 * <T> Function<T, T> identity():返回當前執行的方法,從原始碼中我們也可以看到它返回的是當前的t */ public static void main(String[] args) { // 傳入給定字串,返回轉換後的Integer型別 Function<String,Integer> fun1 = new Function<String, Integer>() { @Override public Integer apply(String s) { Integer convert = Integer.valueOf(s); System.out.println("通過匿名內部類的方式獲取到的值:"+ convert +",資料型別是否為Integer?結果:" + (convert instanceof Integer)); return convert; } }; // 執行該方法的時候,它會去執行我們上述實現的apply方法。 fun1.apply("10086"); // 通過lambda表示式的方式進行實現 Function<String,Integer> fun2 = s->{ Integer convert = Integer.valueOf(s); System.out.println("通過lambda表示式的方式獲取到的值:"+ convert +",資料型別是否為Integer?結果:" + (convert instanceof Integer)); return convert; }; fun2.apply("10000"); /** * 通過匿名內部類的方式獲取到的值:10086,資料型別是否為Integer?結果:true * 通過lambda表示式的方式獲取到的值:10000,資料型別是否為Integer?結果:true * 使用lambda表示式,我們只需要記住參數列和執行邏輯即可,其他的我們無需關注。 */ // 我們繼續對Function的API做一些理解和補充,畢竟這玩意在工作中經常會用上 // andThen 我們都知道常用於鏈式呼叫的,這裡必須保證T和V型別是一樣的,也就是引數泛型T和返回值泛型V Function<String,String> fun3 = x-> { System.out.println("我是fun3的方法"); return x; }; Function<String,String> fun4 = y-> { System.out.println("我是fun4的方法"); return y; }; fun3.andThen(fun4).apply("test"); /** * 最終結果: * 我是fun3的方法 * 我是fun4的方法 */ // 我們發現這裡是先執行fun3的apply方法再執行fun4的apply方法的。 // compose 與andThen一樣都是鏈式呼叫,但結果卻大大不同,這裡必須保證T和V型別是一樣的,也就是引數泛型T和返回值泛型V fun3.compose(fun4).apply("test"); /** * 最終結果: * 我是fun4的方法 * 我是fun3的方法 */ // 我們發現這裡是先執行的fun4的apply方法再執行fun3的apply方法的 // 由此我們推斷出compose和andThen的區別就在於,compose介面方法執行順序從右到左,而andThen則是從左到右。 Function<Object, Object> identity = Function.identity(); // Function.identity() 靜態方法這裡就不好演示了,這個通常在後面搭配Stream流轉Map型別的時候用到,它返回本身 } }
public class Main{ /** * Function<T,R>函數型的函數式介面,泛型T是引數、泛型R則是返回值、主要應用場景做資料型別轉換等。 * R apply(T t): 抽象方法實現,用於呼叫方法並返回泛型R * <V> Function<T, V> andThen: 預設實現方法,內部允許我們鏈式呼叫,與其他的andThen原理一致。 * <V> Function<V, R> compose: 預設實現方法,內部允許我們鏈式呼叫,呼叫方式與andThen一樣,但執行順序不一樣,compose是先執行compose中的函數介面,再執行左邊呼叫的函數介面,依次往左 * <T> Function<T, T> identity():返回當前執行的方法,從原始碼中我們也可以看到它返回的是當前的t */ public static void main(String[] args) { List<Person> persons = Arrays.asList(new Person(1,"張三"),new Person(2,"李四")); // 給定一個person物件集、轉換成姓名屬性集合返回 Function<List<Person>,List<String>> fun1 = list-> { List<String> arr = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { arr.add(list.get(i).getName()); } return arr; }; List<String> personNames = fun1.apply(persons); System.out.println(personNames); /** * 最終結果: * 結果:[張三, 李四] */ } } class Person{ private Integer id; private String name; public Person(Integer id, String name) { this.id = id; this.name = name; } public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public Integer getId() { return id; } public String getName() { return name; } }
1.函數式介面的本質實際上就是將函數以引數的形式進行傳遞
2.Function是函數型的函數式介面,通常用於構建某個物件處理後返回撥用環境
3.除了Function之外,還有各種函數型的函數式介面,還有BIFunction、ToIntFunction等。
Predicate<T> 斷言型的函數式介面,泛型T是引數、返回結果型別為布林型別的函數介面。
public class Main{ /** * Predicate<T>斷言型的函數式介面,泛型T是引數、返回結果型別為布林型別的函數介面。 * boolean test(T t): 抽象方法實現,用於返回傳入的引數邏輯運算後布林型別結果 * Predicate<T> and: 預設實現方法,內部允許我們鏈式呼叫,用於同時判定多個Predicate函數介面的實現 類似於邏輯運算中的短路&操作。 * Predicate<T> negate: 預設實現方法,內部允許我們鏈式呼叫,用於同時判定多個Predicate函數介面的實現,用於將當前判定結果取反後返回,類似於邏輯運算中的!操作 * Predicate<T> or:預設實現方法,內部允許我們鏈式呼叫,用於同時判定多個Predicate函數介面的實現 類似於邏輯運算中的||操作。 * Predicate<T> isEqual:靜態方法,內部允許我們鏈式呼叫,在保證引數不是空的情況下它內部實現邏輯實際上呼叫的是Object的equals,具體equals看子類有沒有重寫 */ public static void main(String[] args) { Predicate<String> pre1 = new Predicate<String>() { @Override public boolean test(String o) { boolean bool = o.matches("[0-9]{1,}"); System.out.println("通過匿名內部類的方式獲取到的值:"+ bool); return bool; } }; // 執行該方法的時候,它會去執行我們上述實現的test方法。 pre1.test("10086"); Predicate<String> pre2 = text->{ boolean bool = text.matches("[0-9]{1,}"); System.out.println("通過lambda表示式的方式獲取到的值:"+ bool); return bool; }; pre2.test("10086a"); /** * 最終結果: * 通過匿名內部類的方式獲取到的值:true * 通過lambda表示式的方式獲取到的值:false * 使用lambda表示式,我們只需要記住參數列和執行邏輯即可,其他的我們無需關注。 */ // 我們繼續對Predicate的API做一些理解和補充,畢竟這玩意在工作中經常會用上 // and 實際上等價於邏輯運運算元中的短路&操作 Predicate<String> fun3 = x-> { System.out.println("先計算fun3"); return true; }; Predicate<String> fun4 = x-> { System.out.println("先計算fun4"); return false; }; System.out.println("第一次and結果:"+fun3.and(fun4).test("test")); /** * 最終結果: * 先計算fun3 * 先計算fun4 * 本次結果:false */ // 那麼為什麼我們知道它是短路&的操作 而不是&的操作呢?,我們只需要將第一個函數式介面返回false,看看它還會不會執行第二個函數式介面即可 Predicate<String> fun5 = x-> { System.out.println("先計算fun5"); return false; }; Predicate<String> fun6 = x-> { System.out.println("先計算fun6"); return true; }; System.out.println("第二次and結果:"+fun5.and(fun6).test("test")); /** * 最終結果: * 先計算fun5 * 第二次and結果:false。 */ // 從結果我們其實可以推斷出,在第一個結果為true的情況下第二個fun6壓根沒進,所以是短路& // 並且起始在and方法原始碼中給我們也可以看到 return (t) -> test(t) && other.test(t); 是短路& // negate 實際上等價於邏輯運運算元中的!操作 // 我們直接取上面的值做例子,本來結果應該為false,取反後應該為true System.out.println("negate結果:"+fun5.and(fun6).negate().test("test")); /** * 最終結果: * negate結果:true */ // or 等價於邏輯運運算元中的||操作 // 我們直接取上面的做例子,第一個為false,第二個為true、||的最終結果應該為true System.out.println("or結果:"+fun5.or(fun6).test("test")); /** * 最終結果: * or結果:true */ // isEqual 內部呼叫的是Object的equals方法,如果子類重寫了equals則調起子類的equals方法 // 如我們常用的String就重寫了Object的equals方法,我們以它做例子 Predicate<String> fun7 = Predicate.isEqual("Hello"); System.out.println("isEquals第一次結果:"+ fun7.test("Hello")); /** * 最終結果: * isEquals第一次結果:true */ Predicate<String> fun8 = Predicate.isEqual("World"); System.out.println("isEquals第二次結果:"+ fun8.test("Hello")); // 自定義的物件型別也是可以比較的,但需要重寫equals和hashCode,這裡就不寫範例了,可以自己玩玩 // 以上就是Predicate的相關API的介紹 } }
public class Main{ /** * Predicate<T>斷言型的函數式介面,泛型T是引數、返回結果型別為布林型別的函數介面。 * boolean test(T t): 抽象方法實現,用於返回傳入的引數邏輯運算後布林型別結果 * Predicate<T> and: 預設實現方法,內部允許我們鏈式呼叫,用於同時判定多個Predicate函數介面的實現 類似於邏輯運算中的短路&操作。 * Predicate<T> negate: 預設實現方法,內部允許我們鏈式呼叫,用於同時判定多個Predicate函數介面的實現,用於將當前判定結果取反後返回,類似於邏輯運算中的!操作 * Predicate<T> or:預設實現方法,內部允許我們鏈式呼叫,用於同時判定多個Predicate函數介面的實現 類似於邏輯運算中的||操作。 * Predicate<T> isEqual:靜態方法,內部允許我們鏈式呼叫,在保證引數不是空的情況下它內部實現邏輯實際上呼叫的是Object的equals,具體equals看子類有沒有重寫 */ public static void main(String[] args) { // 判斷給定字串是否純數位並且小於10 可以使用and進行鏈式呼叫 Predicate<String> pre1 = ((Predicate<String>) s -> s.matches("[0-9]{1,}")).and(x->Integer.valueOf(x) <10); System.out.println("使用and方式進行鏈式呼叫:"+pre1.test("9")); // 需要注意的是:要使用這種方式,第一個Predicate要進行鏈式呼叫必須要強行再指定為(Predicate)型別,後續的介面才能夠呼叫方法 // 實際上這種方式用的比較少,因為比較麻煩,所以一般都會直接使用&&進行判定 Predicate<String> pre2 = s-> s.matches("[0-9]{1,}") && Integer.valueOf(s) <10; System.out.println("使用&&方式呼叫:"+pre2.test("10")); /** * 最終結果: * 使用and方式進行鏈式呼叫:true * 使用&&方式呼叫:false */ } }
以上就是Jdk8提供的基礎的四大函數(除了這四個大的分類還有許多函數式介面,也可以自定義函數式介面實現我們的需求)的基本使用方式和一些簡單案例,具體該怎麼做怎麼寫則需要根據專案實際需求進行,通常函數式介面都會搭配Stream成套使用,目前也有很多框架支援函數式介面的方式、如MyBatis-plus等社群活躍度較高的框架。