java中的Lambda表達式使用及說明
一、介紹:Stream的“黃金搭檔”
Lambda 表達式是 Java 8 引入的 函數(shù)式編程語法糖,核心作用是 簡化函數(shù)式接口的實現(xiàn)代碼(替代冗余的匿名內(nèi)部類),也是 Stream 流式操作的 “黃金搭檔”—— 沒有 Lambda,Stream 的鏈式調(diào)用會變得臃腫難讀。
二、先搞懂:Lambda 只為 “函數(shù)式接口” 服務(wù)
Lambda 表達式不能單獨使用,它的唯一用途是 快速實現(xiàn) “函數(shù)式接口”:
- 函數(shù)式接口:只有 一個抽象方法 的接口(可以有默認方法、靜態(tài)方法);
- 常見的函數(shù)式接口:
Predicate(判斷條件)、Function(數(shù)據(jù)轉(zhuǎn)換)、Consumer(消費數(shù)據(jù))、Runnable(無參無返回)等(Stream 操作的參數(shù)全是這類接口)。
舉個對比:用匿名內(nèi)部類 vs Lambda 實現(xiàn) Runnable(線程任務(wù))
// 1. 傳統(tǒng)匿名內(nèi)部類(冗余)
new Thread(new Runnable() {
@Override
public void run() { // 唯一抽象方法
System.out.println("匿名內(nèi)部類執(zhí)行線程任務(wù)");
}
}).start();
// 2. Lambda 表達式(簡潔)
new Thread(() -> System.out.println("Lambda 執(zhí)行線程任務(wù)")).start();Lambda 自動 “對接” 函數(shù)式接口的唯一抽象方法,省去了 new 接口()、@Override、方法名等冗余代碼。
舉個簡單的例子:
假設(shè)你要給朋友寄一本書(對應(yīng) “一段核心邏輯”):
匿名內(nèi)部類:
就像把書放進一個完整的快遞箱 —— 要選箱子(定義匿名類)、填快遞單(實現(xiàn)接口方法)、封箱(寫方法體),哪怕箱子比書還大(冗余代碼比核心邏輯多),也必須按流程來。比如用 Comparator 排序:
// 匿名內(nèi)部類:“完整快遞箱”,冗余代碼多
List<Integer> list = Arrays.asList(3,1,2);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return a - b; // 核心邏輯:“書”
}
});Lambda 表達式:
就像 “裸奔快遞”—— 直接把書交給快遞員,不用箱子、不用填復(fù)雜快遞單,只說 “送到 XX 地址”(核心邏輯)。多余的 “包裝”(類定義、方法聲明)全省略,直奔主題。同樣的排序邏輯,Lambda 簡化后:
// Lambda:“裸奔快遞”,只留核心邏輯 list.sort((a, b) -> a - b);
匿名內(nèi)部類是 “裹腳布式代碼”,Lambda 是 “緊身衣式代碼”—— 去掉所有不必要的冗余,只保留核心邏輯,讓代碼從 “臃腫” 變 “精干”。
傳遞行為:把 “做事的方法” 當 “快遞包裹” 傳遞(函數(shù)式接口的核心)
生活中,你可以把 “做飯” 這個行為(不是做好的飯)告訴別人:比如讓媽媽 “按我的方法做飯”(傳遞行為),而不是自己先做好飯再交給媽媽(傳遞結(jié)果)。
Lambda 就是 “傳遞行為” 的代碼版 —— 它不直接執(zhí)行邏輯,而是把 “邏輯本身” 當作參數(shù)傳給方法,讓方法按需執(zhí)行。
舉例:用 Stream 過濾列表(傳遞 “過濾規(guī)則” 這個行為):
List<String> fruits = Arrays.asList("蘋果", "香蕉", "橙子", "小番茄");
// 傳遞“長度>2”這個行為給 filter 方法
List<String> longFruits = fruits.stream()
.filter(f -> f.length() > 2) // Lambda:傳遞“過濾行為”
.collect(Collectors.toList());普通代碼是 “傳遞結(jié)果”(比如先過濾好列表再傳入方法),Lambda 是 “傳遞菜譜”(告訴方法 “怎么過濾”,讓方法自己執(zhí)行)—— 靈活度拉滿,同一個方法(如 filter)可以接收不同 “行為” 實現(xiàn)不同功能(過濾長度 > 2、過濾包含 “果” 字等)。
方法引用是 “快遞的‘加急件’”
如果 Lambda 里的邏輯只是 “直接調(diào)用某個已有的方法”(沒有額外加工),就像快遞是 “直接轉(zhuǎn)寄”(不用拆包、不用額外處理),這時可以用方法引用進一步簡化 —— 相當于給快遞貼了 “加急標簽”,跳過中間冗余的 “Lambda 語法糖”,直接指向目標方法。
- Lambda 寫法:就像轉(zhuǎn)寄快遞時,你還要跟快遞員說 “把這個包裹交給 XX”(多余的 “口頭指示”);
- 方法引用寫法:直接在包裹上寫 “收件人:XX”,快遞員直接送達,不用你多廢話。
舉例:按字符串長度排序:
// Lambda 寫法:“口頭指示”快遞員 list.sort((s1, s2) -> s1.length() - s2.length()); // 方法引用寫法:“直接寫收件人”,更簡潔 list.sort(Comparator.comparingInt(String::length));
Lambda 捕獲的變量是 “快遞單上的固定信息”—— 一旦下單(定義 Lambda),信息就不能改了,否則快遞員(JVM)不知道該按哪個信息執(zhí)行。
異常處理:不能 “藏著掖著” 未聲明的 checked 異常
就像寄快遞時不能隱瞞 “易碎品”(否則損壞不賠),Lambda 里如果拋出 checked 異常(如 IOException),必須明確處理(try-catch)或讓包含它的方法聲明拋出 —— 不能偷偷拋出,否則編譯器會 “拒收”。
// 錯誤:未處理 checked 異常
List<File> files = Arrays.asList(new File("a.txt"), new File("b.txt"));
files.stream().map(file -> file.getCanonicalPath()); // getCanonicalPath 拋出 IOException
// 正確:要么 try-catch,要么聲明異常
files.stream().map(file -> {
try {
return file.getCanonicalPath();
} catch (IOException e) {
throw new RuntimeException(e); // 轉(zhuǎn)為 unchecked 異常
}
});Lambda 里的 checked 異常是 “快遞里的易碎品”—— 必須提前告訴 “快遞員”(編譯器)怎么處理(try-catch),否則 “快遞”(代碼)無法正常投遞(編譯報錯)。
三、Lambda 核心語法:(參數(shù)) -> 代碼塊
語法格式:(參數(shù)列表) -> { 業(yè)務(wù)代碼 },有 3 個關(guān)鍵部分:
| 部分 | 說明 |
|---|---|
| (參數(shù)列表) | 對應(yīng)函數(shù)式接口抽象方法的參數(shù)(可省略參數(shù)類型,單參數(shù)可省略括號) |
| -> | 箭頭運算符,分隔參數(shù)和代碼塊(固定寫法) |
| { 代碼塊 } | 對應(yīng)抽象方法的實現(xiàn)邏輯(單條語句可省略大括號,有返回值時可省略 return) |
語法簡化規(guī)則(越用越簡潔)
- 省略參數(shù)類型:Java 能通過接口自動推斷參數(shù)類型;
- 單參數(shù)省略括號:只有 1 個參數(shù)時,
(a)可簡化為a; - 單語句省略大括號:代碼塊只有 1 行時,
{ ... }可省略; - 單返回語句省略 return:若代碼塊是返回語句,省略大括號的同時可省略
return。
語法示例(以Comparator接口為例,用于排序)
// 原始 Lambda(無簡化)
Comparator<Integer> comp1 = (Integer a, Integer b) -> {
return b - a; // 降序排序
};
// 簡化1:省略參數(shù)類型(Java 自動推斷)
Comparator<Integer> comp2 = (a, b) -> {
return b - a;
};
// 簡化2:單返回語句省略大括號和 return
Comparator<Integer> comp3 = (a, b) -> b - a;
// 最終簡化結(jié)果(Stream 中直接使用)
List<Integer> list = Arrays.asList(3,1,2);
list.stream().sorted((a, b) -> b - a).forEach(System.out::println); // 輸出 3,2,1四、Lambda 與 Stream 的 “天作之合”
Stream 的中間操作(filter、map 等)和終端操作(forEach、sorted 等),參數(shù)全是函數(shù)式接口 ——Lambda 剛好能簡化這些接口的實現(xiàn),讓 Stream 鏈式調(diào)用更流暢。
Stream 常見操作 | 函數(shù)式接口 | 接口抽象方法 | Lambda 示例(實現(xiàn)邏輯) |
|---|---|---|---|
| filter() | Predicate<T> | boolean test(T t) | emp -> emp.getAge() >= 30(篩選 30 歲以上員工) |
| map() | Function<T,R> | R apply(T t) | emp -> emp.getSalary()(提取員工工資) |
| forEach() | Consumer<T> | void accept(T t) | System.out::println(打印元素,方法引用簡化) |
| sorted() | Comparator<T> | int compare(T a,T b) | (a,b) -> b - a(降序排序) |
| findAny() | Predicate<T> | boolean test(T t) | n -> n % 2 == 0(找任意偶數(shù)) |
示例:
List<Employee> empList = Arrays.asList(
new Employee("張三", 28, 15000),
new Employee("李四", 32, 18000),
new Employee("趙六", 35, 20000)
);
// 需求:篩選30歲以上員工 → 提取姓名 → 按姓名長度降序 → 打印
empList.stream()
.filter(emp -> emp.getAge() >= 30) // Predicate:篩選條件
.map(Employee::getName) // Function:提取姓名(方法引用簡化Lambda)
.sorted((name1, name2) -> name2.length() - name1.length()) // Comparator:排序
.forEach(System.out::println); // Consumer:打印
// 輸出:趙六(2字)、李四(2字)(若有3字姓名會排在前面)五、Lambda 簡化技巧:方法引用(::)
當 Lambda 的代碼塊只是 調(diào)用一個已存在的方法(無額外邏輯),可以用 類名::方法名 或 對象::方法名 進一步簡化,這就是 方法引用。
| 方法引用類型 | 語法格式 | Lambda 等效寫法 | 示例(Stream 中) |
|---|---|---|---|
| 靜態(tài)方法引用 | 類名::靜態(tài)方法 | (a,b) -> 類名.靜態(tài)方法(a,b) | Integer::compare → (a,b) -> Integer.compare(a,b) |
| 實例方法引用(對象) | 對象::實例方法 | (x) -> 對象.實例方法(x) | System.out::println → (s) -> System.out.println(s) |
| 實例方法引用(參數(shù)) | 類名::實例方法 | (a,b) -> a.實例方法(b) | String::equals → (s1,s2) -> s1.equals(s2) |
| 構(gòu)造器引用 | 類名::new | (參數(shù)) -> new 類名(參數(shù)) | ArrayList::new → () -> new ArrayList<>() |
示例:
// 1. 實例方法引用(對象):System.out::println 替代 (s) -> System.out.println(s)
List<String> names = Arrays.asList("張三", "李四");
names.stream().forEach(System.out::println);
// 2. 實例方法引用(參數(shù)):String::length 替代 (s) -> s.length()
List<Integer> nameLengths = names.stream()
.map(String::length) // 提取姓名長度
.collect(Collectors.toList());
// 3. 構(gòu)造器引用:ArrayList::new 替代 () -> new ArrayList<>()
List<String> newNames = names.stream()
.filter(name -> name.length() == 2)
.collect(Collectors.toCollection(ArrayList::new));六、Lambda 中的變量捕獲
Lambda 可以訪問外部變量,但有嚴格限制:
- 只能訪問 final 或 effectively final 的變量(即變量聲明后不再修改);
- 不能修改外部變量(否則編譯報錯)。
示例:
// 正確:外部變量是 effectively final(聲明后未修改)
int minAge = 30;
empList.stream().filter(emp -> emp.getAge() >= minAge).forEach(System.out::println);
// 錯誤:修改外部變量(編譯報錯)
int count = 0;
empList.stream().forEach(emp -> {
count++; // 報錯:Variable used in lambda expression should be final or effectively final
});原因:Lambda 本質(zhì)是匿名函數(shù),可能在多線程中執(zhí)行,禁止修改外部變量是為了避免線程安全問題。
七、Lambda 常見誤區(qū)
- 誤以為能替代所有匿名內(nèi)部類:只有 “函數(shù)式接口”(單抽象方法)才能用 Lambda,多抽象方法的接口不行;
- 修改外部變量:違反 “final 或 effectively final” 規(guī)則,編譯報錯;
- 過度簡化導(dǎo)致可讀性下降:復(fù)雜邏輯(多行業(yè)務(wù)代碼)不適合用 Lambda,建議拆分為普通方法,再用方法引用調(diào)用;
- 忽略異常處理:Lambda 中拋出受檢異常(如
IOException),需在代碼塊中捕獲,或函數(shù)式接口的抽象方法聲明該異常。
示例:
// 錯誤:Lambda 中拋出受檢異常,未處理
List<String> files = Arrays.asList("a.txt", "b.txt");
files.stream().map(file -> new FileInputStream(file)); // 編譯報錯:未處理 IOException
// 正確:捕獲異常
files.stream().map(file -> {
try {
return new FileInputStream(file);
} catch (IOException e) {
throw new RuntimeException(e); // 轉(zhuǎn)為運行時異常拋出
}
});八、總結(jié)
Lambda 表達式的核心價值是 “簡化代碼 + 傳遞行為”:
- 專為函數(shù)式接口設(shè)計,替代冗余的匿名內(nèi)部類;
- 語法簡潔靈活,配合 Stream 能大幅提升代碼可讀性和開發(fā)效率;
- 方法引用是 Lambda 的 “進階簡化技巧”,適合簡單的方法調(diào)用場景;
- 注意變量捕獲規(guī)則和異常處理,避免踩坑。
一句話記住 Lambda:“少寫代碼,多傳邏輯”—— 它讓 Java 從 “面向?qū)ο?rdquo; 向 “函數(shù)式編程” 邁出了重要一步,而 Stream 則是 Lambda 最經(jīng)典的應(yīng)用場景。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot SSE服務(wù)端主動推送事件的實現(xiàn)
本文主要介紹了SpringBoot SSE服務(wù)端主動推送事件的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06
SpringBoot整合Mybatis Plus多數(shù)據(jù)源的實現(xiàn)示例
本文主要介紹了SpringBoot整合Mybatis Plus多數(shù)據(jù)源的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11
IDEA 2023創(chuàng)建JSP項目的完整步驟教程
這篇文章主要介紹了IDEA 2023創(chuàng)建JSP項目的完整步驟教程,創(chuàng)建項目需要經(jīng)過新建項目、設(shè)置項目名稱和路徑、選擇JDK版本、添加模塊和工件、配置Tomcat服務(wù)器等步驟,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2024-10-10
使用Mybatis Plus整合多數(shù)據(jù)源和讀寫分離的詳細過程
這篇文章主要介紹了Mybatis Plus整合多數(shù)據(jù)源和讀寫分離的詳細過程,mybatisplus可以整合阿里的分布式事務(wù)組件seata,本文通過示例代碼給大家介紹的非常詳細,需要的朋友參考下吧2021-09-09

