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) | 如filter等方法的函式式介面都是Predicate型別 |
Consumer<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、也可以根據自身需求進行自定義,
Supplier<T>
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>
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
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等社區活躍度較高的框架,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/508989.html
標籤:Java
上一篇:總結一下今天所學的知識9.18
下一篇:微信小程式--云開發
