在兼顧面向對象特性的基礎上,Java語言通過Lambda表達式與方法引用等,為開發者打開了函數式編程的大門。 下面我們做一個初探。
Lambda的延遲執行
有些場景的代碼執行后,結果不一定會被使用,從而造成性能浪費。而Lambda表達式是延遲執行的,這正好可以 作為解決方案,提升性能。
性能浪費的日志案例
注:日志可以幫助我們快速的定位問題,記錄程序運行過程中的情況,以便項目的監控和優化。
一種典型的場景就是對參數進行有條件使用,例如對日志消息進行拼接后,在滿足條件的情況下進行打印輸出:
public class Demo01Logger {
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "World ";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
private static void log(int level, String mgs) {
if (level == 1) {
System.out.println(mgs);
}
}
}
這段代碼存在問題:無論級別 level 是否滿足要求,作為 log 方法的第二個參數,三個字符串一定會首先被拼接並傳入方法內,然后才會進行級別判斷。如果級別不符合要求,那么字符串的拼接操作就白做了,存在性能浪費。
備注:SLF4J是應用非常廣泛的日志框架,它在記錄日志時為了解決這種性能浪費的問題,並不推薦首先進行 字符串的拼接,而是將字符串的若干部分作
為可變參數傳入方法中,僅在日志級別滿足要求的情況下才會進 行字符串拼接。例如: LOGGER.debug("變量{}的取值為{}。", "os", "macOS") ,
其中的大括號 {} 為占位 符。如果滿足日志級別要求,則會將“os”和“macOS”兩個字符串依次拼接到大括號的位置;否則不會進行字 符串拼接。這也是
一種可行解決方案,但Lambda可以做到更好。
體驗Lambda的更優寫法
使用Lambda必然需要一個函數式接口:
@FunctionalInterface
public interface MessageBuilder {
/**
* 信息生成器
* @return 生成的信息
*/
public abstract String builderMessage();
}
然后對 log 方法進行改造:
public class Demo02Logger {
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "World ";
String msgC = "Java";
log(1, () -> msgA + msgB + msgC);
}
private static void log(int level, MessageBuilder mb) {
if (level == 1) {
System.out.println(mb.builderMessage());
}
}
}
改造前后的對比:
這樣一來,只有當級別滿足要求的時候,才會進行三個字符串的拼接;否則三個字符串將不會進行拼接。
證明Lambda的延遲
下面的代碼可以通過結果進行驗證:
public class Demo03Logger {
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "World ";
String msgC = "Java";
log(2, () -> {
System.out.println("Lambada 執行!");
return msgA + msgB + msgC;
});
}
private static void log(int level, MessageBuilder mb) {
if (level == 1) {
System.out.println(mb.builderMessage());
}
}
}
這里只是在調用 log 方法的時候,將傳入的Lambda稍作修改,
當傳入的 level = 1 的時候,控制台輸出:
Lambada 執行!
Hello World Java
當傳入的 level != 1 的時候,控制台沒有輸出。
從結果中可以看出,在不符合級別要求的情況下,Lambda將不會執行。從而達到節省性能的效果。
擴展:實際上使用內部類也可以達到同樣的效果,只是將代碼操作延遲到了另外一個對象當中通過調用方法來完成。而是否調
用其所在方法是在條件判斷之后才執行的。
public class Demo04Logger {
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "World ";
String msgC = "Java";
log(1, new MessageBuilder() {
@Override
public String builderMessage() {
System.out.println("Lambada 執行!");
return msgA + msgB + msgC;
}
});
}
private static void log(int level, MessageBuilder mb) {
if (level == 1) {
System.out.println(mb.builderMessage());
}
}
}
使用Lambda作為參數和返回值
如果拋開實現原理不說,Java中的Lambda表達式可以被當作是匿名內部類的替代品。如果方法的參數是一個函數式接口類型,那么就可以使用Lambda表達式進行替代。使用Lambda表達式作為方法參數,其實就是使用函數式 接口作為方法參數。
Lambda作為參數
例如 java.lang.Runnable 接口就是一個函數式接口,假設有一個 startThread 方法使用該接口作為參數,那么就可以使用Lambda進行傳參。這種情況其實和 Thread 類的構造方法參數為 Runnable 沒有本質區別。
匿名內部類作為參數,創建新的線程並執行:
public class Demo01Runnable {
public static void main(String[] args) {
startThread(new Runnable() {
@Override
public void run() {
System.out.println("線程任務執行!");
}
});
}
/**
* 創建一個新的線程,賦予任務,然后開啟線程
* @param runnable 傳入Thread類的接口,實現創建新線程
*/
private static void startThread(Runnable runnable) {
new Thread(runnable).start();
}
}
運行程序,控制台輸出:
線程任務執行!
Lambda作為參數,創建新的線程並執行:
public class Demo02Runnable {
public static void main(String[] args) {
startThread(
() -> System.out.println("線程任務執行!")
);
}
/**
* 創建一個新的線程,賦予任務,然后開啟線程
* @param runnable 傳入Thread類的接口,實現創建新線程
*/
private static void startThread(Runnable runnable) {
new Thread(runnable).start();
}
}
運行程序,控制台輸出:
線程任務執行!
Lambda作為返回值
類似地,如果一個方法的返回值類型是一個函數式接口,那么就可以直接返回一個Lambda表達式。當需要通過一個方法來獲取一個 java.util.Comparator 接口類型的對象作為排序器時,就可以調該方法獲取。
Lambda作為返回值,字符串的長短比較:
import java.util.Arrays;
import java.util.Comparator;
public class DemoComparator {
public static void main(String[] args) {
String[] array = { "abc", "ab", "a" };
System.out.println("使用比較器比較之前:" + Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println("使用比較器比較之后:" + Arrays.toString(array));
}
/**
* 字符串a、b的長短比較,自己定義比較器規則,生序排序,字符串長的排在后面。
* @return 布爾值,
* a.length() - b.length() < 0 返回 false,
* a.length() - b.length() > 0 返回 true,
* a.length() = b.length() 返回 0
*/
public static Comparator<String> newComparator() {
return (a, b) -> a.length() - b.length();
}
}
匿名內部類作為返回值,字符串的長短比較:
import java.util.Arrays;
import java.util.Comparator;
public class DemoComparator {
public static void main(String[] args) {
String[] array = { "abc", "ab", "a" };
System.out.println("使用比較器比較之前:" + Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println("使用比較器比較之后:" + Arrays.toString(array));
}
public static Comparator<String> newComparator1() {
return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};
}
}
運行程序,控制台輸出一樣:
使用比較器比較之前:[abc, ab, a]
使用比較器比較之后:[a, ab, abc]