函數式接口在Java指的是:有且僅有一個抽象方法的接口就稱爲函數式接口。java
函數式接口,適用於函數式編程的,在Java當中的函數式編程體如今Lambda,因此函數式接口就是用來服務Lambda表達式。只有確保接口當中有且僅有一個抽象方法,Java中的Lambda才能順利進行推導。mysql
備註:"語法糖"是指使用更加便利方便,可是原理不變的代碼語法。就好比遍歷集合時使用for-each語法,其實底層使用的是迭代器,這即是"語法糖"。sql
只有確保接口當中有且僅有一個抽象方法便可:編程
修飾符 interface InterfaceName{ // 只能定義一個抽象方法 public abstract 返回值類型 方法名稱(參數列表); // 還能夠定義其餘的非抽象方法 }
示例:數組
public interface FunctionInterfaceOne { public abstract void show01(); public default void show02(){ //..... } // void show03(); 有且僅有一個抽象方法,才稱爲函數式接口 }
與@Override註解做用相似,Java 8中專門爲函數式接口引入的一個新註解@FunctionalInterface
,該註解主要定義在接口上。一旦在接口上使用該註解,編譯期將會強制檢查該接口是否是一個函數式接口,該接口中是否是有且僅有一個抽象方法,若是不是,編譯報錯。tomcat
@FunctionalInterface public interface FunctionInterfaceOne { // 定義一個抽象的方法 void method(); //void show(); default void show02(){ } }
對於自定義的函數式接口,通常用於方法的參數和返回值上。ide
可以在兼顧Java的面向對象特性基礎上,經過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; }); }
備註:實際上使用內部類也能夠達到這樣的效果,只是將代碼操做延遲到另一個對象當中經過調用方法來完成。
後面的代碼的執行取決於前面的條件的判斷結果。
在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.Comparator
接口是一個函數式接口
代碼以下:
public class Demo02Lambda { // 定義一個方法,方法的返回值類型是一個函數式接口類型Comparator public static Comparator<String> createComparator() { // 返回值就是一個函數式接口 /*return new Comparator() { @Override 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
包中被提供。
java.util.function.Supplier<T>
接口,該接口有且僅有一個無參的方法:T get()
。用來獲取一個泛型參數指定類型的對象數據。因爲該接口是一個函數式接口,因此咱們可使用Lambda表達式來操做它。
Supplier
代碼以下:
// 定義一個方法,方法的參數傳遞一個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 "你好Java"; }); System.out.println(str);*/ // 求一個int類型的數組中的最值 int[] arr = {10,20,5,8,3,50}; int maxNum = getNum(() -> { // 求出數組的最大值 int max = arr[0]; for (int i : arr) { // 判斷 if (max < i) { max = i; } } return max; }); // 輸出最大值 System.out.println(maxNum);// 50 }
java.util.function.Consumer<T>
接口恰好和Supplier接口相反,它不是用來生產一個數據,而是消費一個數據。
數據的類型由泛型來指定。
意思就是消費一個指定泛型的數據。
代碼以下:
// 定義一個方法,方法的參數傳遞一個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 str = name.toUpperCase(); String s = new StringBuffer(str).reverse().toString(); System.out.println(s);// GFEDCBA }); }
若是一個方法的參數和返回值全都是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("Java31-中國最棒-都是業界大佬", (name1)->{ // 消費規則 // 截取傳入的字符串 String sub = name1.substring(0, 6); System.out.println(sub); }, (name2) -> { // 定義消費的規則 分紅字符串數組展現 String[] strs = name2.split("-"); System.out.println(Arrays.toString(strs));// {「Java31","中國最棒","都是業界大佬"} }); }
經過查看源碼得知:andThen方法不容許傳入一個null對象不然就會拋出空指針異常。
要想把兩次消費的動做鏈接起來,須要傳入兩個Consumer接口,經過andThen
方法實現一步一步執行消費動做。
練習:
定義一個字符串數組,存儲每個人的信息如:"張三,20,鄭州市",存儲5我的的信息
使用Consumer接口,按照指定的格式進行打印輸出:姓名:張三;年齡:20;地址:鄭州市
要求將打印姓名的動做做爲第一個Consumer接口的規則
將打印年齡的動做做爲第二個Consumer接口的規則
將打印地址的動做做爲第三個Consumer接口的規則。
最終將三個Consumer接口按照規定的順序拼接輸出出來。
代碼以下:
// 規則 public static void consumers(String[] arr, Consumer<String> con1, Consumer<String> con2, Consumer<String> con3) { // 操做arr數組當中的每個元素 for (String str : arr) { con1.andThen(con2).andThen(con3).accept(str);// 定義了消費的前後的順序 } } public static void main(String[] args) { // 定義一個字符串數組 String[] arr = {"李四,20,南陽市", "張三,20,鄭州市", "小孫,20,開封市", "小麗,20,信陽市", "小趙,20,洛陽市"}; // 調用consumers方法,因爲Consumer接口是一個函數式接口,因此可使用Lambda consumers(arr, one -> System.out.print("姓名:" + one.split(",")[0] + ";"), two -> System.out.print("年齡:" + two.split(",")[1] + ";"), three -> System.out.println("地址:" + three.split(",")[2])); }