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

