一、Lambda 的核心概念
Lambda 表达式可以理解为:一段可以传递的代码(将代码像数据一样传递) ,它的本质是简化后的匿名内部类,但只适用于特定场景 ------ 实现函数式接口(只有一个抽象方法的接口)。
你可以把 Lambda 看作 "语法糖",目的是减少冗余代码,让逻辑更聚焦。比如原本需要写好几行的匿名内部类,用 Lambda 一行就能搞定。
1.1 Lambda表达式的语法
基本语法: (parameters) -> expression 或 (parameters) ->{ statements; }
Lambda表达式由三部分组成:
- paramaters :类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明
也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。 - ->:可理解为"被用于"的意思
- 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不返回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不返回。
java
// 1. 不需要参数,返回值为 2
() -> 2
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的和
(x, y) -> x + y
// 4. 接收2个int型整数,返回他们的乘积
(int x, int y) -> x * y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
1.2 函数式接口
要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法 。
注意:
- 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
- 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。
定义方式:
java
@FunctionalInterface
interface NoParameterNoReturn {
//注意:只能有一个方法
void test();
}
但是这种方式也是可以的:
java
@FunctionalInterface
interface NoParameterNoReturn {
void test();
default void test2() {
System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
}
}
二、Lambda 的基础使用(对比匿名内部类)
先看一个经典场景:用Runnable接口创建线程,对比传统写法和 Lambda 写法。
1. 传统匿名内部类写法
java
public class LambdaBasic {
public static void main(String[] args) {
// 传统方式:匿名内部类实现Runnable接口
Thread t1 = new Thread(new Runnable() {
@Override
public void run() { // Runnable只有一个抽象方法run(),符合函数式接口
System.out.println("传统方式:线程执行");
}
});
t1.start();
}
}
2. Lambda 简化写法
java
public class LambdaBasic {
public static void main(String[] args) {
// Lambda写法:省略接口名、方法名,只保留参数和逻辑
Thread t2 = new Thread(() -> System.out.println("Lambda方式:线程执行"));
t2.start();
// 更复杂的Lambda示例(带参数+多行代码)
Thread t3 = new Thread(() -> {
String msg = "多行代码的Lambda";
System.out.println(msg);
});
t3.start();
}
}
三、Lambda 的核心适用场景:集合操作(Stream API)
Lambda 最常用的场景是配合 Java 8 的Stream API 操作集合(List/Set/Map),替代传统的 for 循环,让代码更简洁、易读。
示例:操作 List 集合
java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LambdaStreamDemo {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("apple");
fruits.add("banana");
fruits.add("orange");
fruits.add("grape");
// 场景1:遍历集合(替代for-each)
System.out.println("遍历集合:");
fruits.forEach(fruit -> System.out.println(fruit)); // 单行Lambda
// 场景2:过滤元素(筛选长度>5的水果)
List<String> longNameFruits = fruits.stream()
.filter(fruit -> fruit.length() > 5) // Lambda作为过滤条件
.collect(Collectors.toList());
System.out.println("\n长度>5的水果:" + longNameFruits); // 输出:[banana, orange]
// 场景3:映射转换(把所有水果转成大写)
List<String> upperFruits = fruits.stream()
.map(fruit -> fruit.toUpperCase()) // Lambda作为转换逻辑
.collect(Collectors.toList());
System.out.println("\n大写水果名:" + upperFruits); // 输出:[APPLE, BANANA, ORANGE, GRAPE]
// 场景4:聚合操作(统计水果名总长度)
int totalLength = fruits.stream()
.mapToInt(fruit -> fruit.length()) // 转换为长度的int流
.sum(); // 求和
System.out.println("\n水果名总长度:" + totalLength); // 输出:20
}
}
四、自定义函数式接口(理解 Lambda 的本质)
Lambda 必须配合函数式接口使用,你也可以自定义函数式接口(@FunctionalInterface注解标记,编译器会检查是否只有一个抽象方法)。
示例:自定义函数式接口
java
// 自定义函数式接口:计算两个数的操作
@FunctionalInterface // 注解可选,但推荐加,用于校验
interface Calculate {
int operate(int a, int b); // 只有一个抽象方法
}
public class CustomFunctionalInterface {
public static void main(String[] args) {
// 用Lambda实现加法
Calculate add = (a, b) -> a + b;
System.out.println("10+20=" + add.operate(10, 20)); // 输出:30
// 用Lambda实现乘法
Calculate multiply = (a, b) -> a * b;
System.out.println("10*20=" + multiply.operate(10, 20)); // 输出:200
// 多行代码的Lambda(比如减法,加日志)
Calculate subtract = (a, b) -> {
System.out.println("执行减法操作:" + a + "-" + b);
return a - b; // 多行代码必须加{}和return
};
System.out.println("10-20=" + subtract.operate(10, 20)); // 输出:-10
}
}
Lambda在集合当中的使用
| 对应的接口 | 新增的方法 |
|---|---|
| Collection | removeIf() spliterator() stream() parallelStream() forEach() |
| List | replaceAll() sort() |
| Map | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent()computeIfPresent() compute() merge() |
以上方法的作用可自行查看我们发的帮助手册。我们这里会示例一些方法的使用。注意:Collection的forEach()方
法是从接口 java.lang.Iterable 拿过来的。
五、Lambda 的关键注意事项
- 变量捕获 :Lambda中可以使用外部的局部变量,但该变量必须是final或"事实上的final"(即没有被重新赋
值);
java
int num = 10; // 事实上的final(没有重新赋值)
Calculate addNum = (a, b) -> a + b + num; // 合法
// num = 20; // 报错:Lambda引用的局部变量不能重新赋值
2.适用范围 :仅能简化函数式接口的实现,不能用于普通类/多抽象方法的接口;
3.可读性:Lambda适合短逻辑,复杂逻辑(超过3行)建议抽成普通方法,避免Lambda嵌套导致代码难懂。
总结
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作
缺点: - 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试