函數語言程式設計介面
函式式介面
概念
函式式介面在java指的是:有且僅有一個抽象方法的介面就稱為函式式介面。
函式式介面,使用於函數語言程式設計,在java當中的函數語言程式設計體現在Lambda,所以函式式介面就是用來服務Lambda表示式。只有確保介面當中有且僅有一個抽象方法,java中的lambda才能順利的進行推導。
備註:語法糖是指使用更加便利方便,但是原理不變的程式碼語法。就比如便利集合的時使用for-each語法,其實底層使用的是迭代器,這便是語法糖。
格式
只有確保介面當中有且僅有一個抽象方法即可:
修飾符 interface InterfaceName(){
//只能定義一個抽象方法
public abstract 返回值型別 方法名稱(引數列表);
//還可以定義其他的非抽象方法
}
示例如下:
public interface FunctionInterfaceOne{
public abstract void show01();
public default void show02(){
}
//.............
//void show03();有且僅有一個抽象方法,才被稱為函式式介面
}
@Functionallnterface註解
與@Overried註解作用型別,java8中專門為函式式介面引入一個新註解@functionalInterface,該註解主要定義在介面上。一旦在介面上使用該註解,編譯期將會強制檢查介面是不是一個函式式介面,該介面中是不是有且僅有一個抽象方法,如果不是,編譯報錯。
@FunctionalInterface
public interface FunctionInterfaceOne {
//定義一個抽象方法
void method();
//void show();//有且僅有一個抽象方法
public default void show(){
}
}
自定義函式式介面的用途:
對於自定義的函式式介面,一般用於方法的引數和返回值上。
函數語言程式設計
能夠在兼顧java的面向物件特性基礎上,通過lambda表示式與後面的方法引用,為開發者開啟函數語言程式設計的大門。
Lambda的延遲載入
有些場景的程式碼執行執行後,結果不一定會被使用到,從未造成效能的浪費。為Lambda表示式是延遲執行的,正好可以解決此問題,提升效能。
public static void main(String[] args) {
// 定義一些日誌資訊
String message1 = "執行mysqld.exe操作";
String message2 = "執行java.exe操作";
String message3 = "執行tomcat.exe操作";
// 呼叫showLog方法,引數是一個函式式介面--BuildLogMessage介面,所以可以使用Lambda表示式
/* showLog(2, () -> {
// 返回一個拼接好的字串
return message1 + message2 + message3;
});*/
// 簡化Lambda表示式
/*
使用Lambda表示式作為引數傳遞,
只有滿足條件,日誌的等級小於等於3
才會呼叫此介面BuildLogMessage當中的方法buildLogMessage
才會進行字串的拼接動作
如果條件不滿足,日誌的等級大於3
那麼BuildLogMessage介面當中的方法buildLogMessage也不會執行
所以拼接字串的動作也不會執行
所以不會存在效能上的浪費。
*/
showLog(4, () -> {
System.out.println("前面的日誌等級大於3,此處不執行!");
return message1 + message2 + message3;
});
}
備註:實際上使用內部類也可以達到這樣的效果,只是將程式碼操作延遲到另外一個物件當中通過呼叫方法來完成。
後面的程式碼的執行取決於前面的條件的判斷結果。
使用Lambda作為方法的引數和返回值
在java當中,Lambda表示式是作為匿名內部類的替代品,如果一個方法的引數是一個函式式介面型別,那麼可以使用Lambda表示式進行替代
java.lang.Runnable介面就是一個函式式介面
程式碼如下:
public class Demo01Lambda{
//定義一個方法,開啟執行緒的方法
public static void startThread(Runnable r){
new Thread(r).start();
}
public static void main(String[] args){
startThread(()->{
System.out.println("開啟一個新執行緒,執行緒任務被提升了!");
});
//優化Lambda
startThread(()->System.out.println("開啟一個新執行緒,執行緒任務被提升了!"));
}
}
如果一個方法的返回值型別是一個函式式介面,那麼我們可以直接使用一個Lambda表示式。
java.util.Comperator介面是一個函式式介面
程式碼如下:
public class Demo02Lambda{
//定義一個方法,方法的返回值型別是一個函式式介面型別Comparator
public static Comparator<String> createComparator(){
//返回值就是一個函式式介面 內部類的方式
/*return new Comparator(){
@Overried
public int compare(String o1,String o2){
//自定義比較的規則 升序/降序
//字串的長度
return o1.length() - o2.length();//升序
}
}*/
//使用Lambda 字串的長度升序
return (o1,o2)-> o1.length() - o2.length();
}
public static void main(String[] args){
String[] strs = {"aaa","a","abcdefg","ggggg"};
Arrays.sort(strs,createComparator());
System.out.println(Arrays.toString(strs));//{"a","aaa","ggggg","abcdefg"}
}
}
常用的函式式介面
JDK提供了大量的常用函式式介面,豐富Lambda表示式的使用場景。他們主要在java.util.function包中被提供。
Supplier介面
java.util.function.Supplier <T> 介面,該介面有且僅有一個無參的方法:T get()。用來獲取一個泛型引數指定型別的物件資料。由於該介面是一個函式式介面,所以我們可以使用Lambda表示式來操作它。
Supplier<T>介面被稱之為生產型介面,指定介面的泛型是什麼型別,那麼介面中的get()方法就會生產什麼型別的資料。
程式碼如下:
//定義一個方法,方法的引數傳遞一個Supplier<T>介面,泛型指定String,get方法就會返回一個String
public static String getString(Supplier<String> sup){
return sup.get();
}
//定義一個方法,方法的引數傳遞一個Supplier<T>介面,泛型指定為Integer型別,get方法就會返回一個int
public static int getNum(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
/* //呼叫getString方法,方法的引數傳遞Supplier<T>是一個函式式介面,那麼我們就可以使用Lambda
String str = getString(() -> {
//生產一個字串並返回
return new String("你好java");
});
System.out.println(str);*/
//求一個int型別的陣列中的最值
int[] arr = {10,20,5,8,3,30};
int maxNum = getNum(() -> {
//求出陣列的最大值
int max = arr[0];
for (int i : arr) {
//判斷
if (max < i) {
max = i;
}
}
return max;
});
System.out.println(maxNum);
}
Consumer介面
**java.util.function.Consumer<T>**介面剛好和Supplier介面相反,它不是用來生產一個數據的,而是用來消費一個數據。
資料的型別由泛型來指定。
accept方法
意思就是消費一個指定型別的資料。
程式碼如下:
//定義一個方法,方法的引數傳遞一個Consumer<String>介面,傳遞一個字串變數
public static void consumer(String str,Consumer<String> con){
//使用消費型介面物件,消費傳遞的字串值
con.accept(str);
}
public static void main(String[] args){
//來呼叫消費方法consumer,Consumer<String>是一個函式式介面型別,所以可以使用Lambda表示式
consumer("abcdefg", name ->{
//把裡面的abcdefg字串改為大寫輸出
String s = name.toUpperCase();
System.out.println(new StringBuffer(s).reverse().toString());
} );
}
預設 的方法:andThen
如果一個方法的引數和返回值全都是Consumer型別的,那麼就可以實現這樣的效果:消費資料的時候,首先做一個消費的操作,再做一次消費的操作,實現組合。可以通過Consumer介面當中的預設方法:andThen來實現。
程式碼示例如下:
//定義一個方法,方法的引數傳遞一個字串和兩個consumer介面,Consumer介面的泛型指定為字串
public static void consumers(String str, Consumer<String> con1,Consumer<String> con2){
/*con1.accept(str);
con2.accept(str);*/
//andThen 連續消費 default Consumer<String> andThen
//先執行左邊的consumer--con1動作,andThen---->再次執行Consumer--con2動作
con1.andThen(con2).accept(str);
//規則 con1連線con2,先執行con1消費資料,在執行con2消費資料
}
public static void main(String[] args) {
//由於consumers方法的引數Consumer介面是一個函式式介面,可以使用Lambda表示式
consumers("Java-中國最棒-都是業界大佬", name1->{
//消費規則
//擷取傳入的字串
String substring = name1.substring(0, 6);
System.out.println(substring);
},name2-> {
//定義消費規則 分成字串陣列進行展示
String[] strs = name2.split("-");
System.out.println(Arrays.toString(strs));//{"Java","中國最棒","都是業界大佬"}
});
}
通過檢視原始碼得知:endThen方法不允許傳入一個null物件否則就會丟擲一個控制針異常。
要想把兩次消費的動作連線起來,需要傳入兩個consumer介面,通過endThen方法實現一步一步執行消費動作。
例如一個小例子:
/*定義一個字串陣列,儲存一個人的資訊:"張三,20,鄭州市",,儲存5個人的資訊
使用Consumer介面按照指定的格式進行一個列印輸出:姓名:張三;年齡:20;地址:鄭州市
要求將列印姓名的動作作為第一個consumer介面的規則
將列印年齡的動作作為第二個consumer介面的規則
將列印地址的動作作為第三個consumer介面的規則*/
//定義一個方法 方法的引數為一個Strng字串陣列 三個Consumer介面
public static void consumer(String str[], Consumer<String> con1, Consumer<String> con2, Consumer<String> con3) {
for (String s : str) {
con1.andThen(con2).andThen(con3).accept(s);
System.out.println();
}
}
public static void main(String[] args) {
String[] str = new String[5];
str[0] = "張三,20,北京";
str[1] = "李四,20,上海";
str[2] = "王五,20,深圳";
str[3] = "趙六,20,廣州";
str[4] = "田七,20,蘇州";
consumer(str, name->
System.out.print("姓名:" + name.split(",")[0] + ";"),
age->
System.out.print("年齡:" + age.split(",")[1] + ";"),
address->
System.out.print("地址:" + address.split(",")[2]));
}