前言
λ ,形似汉字的“入”,希腊字母表中排序第十一位的字母,英语名称为 Lambda
。
Lambda 表达式,也可称为闭包,是 JDK 8 的一个新特性,它可以取代大部分的匿名内部类,取消了模板,允许用函数式风格编写代码。这样写出更优雅的 Java 代码,可读性更好,表达更清晰,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
使用前提
并不是所有的接口都可以使用 Lambda 表达式来实现,Lambda 规定函数式接口才能用。如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static
方法),那么该接口就是函数式接口。Java 中内置了不少的函数式接口。
JDK 8 又新增了一个包:java.util.function
,在这个包里有很多类,不多介绍。
@FunctionalInterface
@FunctionalInterface 注解就是用来指定某个接口必须是函数式接口,它只能修饰接口,不能修饰其它程序元素;作用是让编译器强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
虽然 @FunctionalInterface 注解不是必须的,但自定义的函数式接口最好还是加上,一是按照规范养成良好的编程习惯,二是帮助他人一看到这个注解就知道是函数式接口,避免一些不必要的低级错误。
基础语法
Lambda 表达式由三部分组成:参数列表、箭头、主体。语法格式如下:
// 参数列表 + 箭头 + 表达式
(parameters) -> expression
// 参数列表 + 箭头 + 大括号和返回语句
(parameters) -> { statements; }
说明:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号,但是没有参数不能省略空括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
练习用例
自定义 6 个函数式接口,用于说明。
/** * 无参数,无返回值 */ @FunctionalInterface public interface NoParamAndNoReturn { void sayHello(); } /** * 无参数,有返回值 */ @FunctionalInterface public interface NoParamButReturn { String sayHello(); } /** * 一个参数,无返回值 */ @FunctionalInterface public interface OneParamButNoReturn { void sayHello(String name); } /** * 一个参数,有返回值 */ @FunctionalInterface public interface OneParamAndReturn { String sayHello(String name); } /** * 多个参数,无返回值 */ @FunctionalInterface public interface MultiParamButNoReturn { void sayHello(String name, int age); } /** * 多个参数,有返回值 */ @FunctionalInterface public interface MultiParamAndReturn { String sayHello(String name, int age); }
然后再新建一个测试类,用于实现以上 6 个接口并测试运行。在实现接口时,IDEA 会智能提示:
Anonymous new NoParamAndNoReturn() can be replaced with lambda
,因为 Lambda 表达式能简化某些匿名内部类的写法。
为了简单起见,直接使用 Lambda 表达式,按照规则,能不写的都不写。- 简化参数类型,但是必须所有参数都不写,要么都写要么都不写。
- 简化参数小括号,如果只有一个参数则,可以省略参数小括号。
- 简化方法体大括号,如果方法条只有一条语句,则可以省略方法体大括号。
- 如果方法体只有一条语句,并且是
return
语句,则可以省略方法体大括号。 - 参数的命名可以简短一些,可以使用 a、b、c,但是为了可读性,最好还是用原方法的参数名。
按照以上规则简化,代码如下:
public class FunctionTest {
public static void main(String[] args) {
// 1.无参数,无返回值
NoParamAndNoReturn npnr = () -> System.out.println("Hello, World!");
npnr.sayHello();
// 2.无参数,有返回值
NoParamButReturn npbr = () -> "Hello, 2021!";
String msg1 = npbr.sayHello();
System.out.println(msg1);
// 3.一个参数,无返回值
OneParamButNoReturn opbnr = name -> System.out.println("你好,你可以叫我" + name);
opbnr.sayHello("靓仔");
// 4.一个参数,有返回值
OneParamAndReturn opar = name -> {
System.out.println("执行了一个参数,有返回值的方法");
return name + ",我爱你!";
};
String msg2 = opar.sayHello("中国");
System.out.println(msg2);
// 5.多个参数,无返回值
MultiParamButNoReturn mpbnr = (name, age) -> System.out.println("我叫" + name + ",今年" + age + "岁了");
mpbnr.sayHello("靓仔", 21);
// 6.多个参数,有返回值。参数的命名更简短了,但无法见名知义
MultiParamAndReturn mpar = (a, b) -> a + "现在" + (b - 1949) + "岁了";
String msg3 = mpar.sayHello("新中国", 2021);
System.out.println(msg3);
}
}
方法引用
有时候不是必须要重写某个匿名内部类的方法,箭头“->”右边执行的表达式只是调用一个类已有的方法,那么利用 Lambda 表达式的方法引用(Method Reference
)。
方法引用的格式:方法的归属者 + 一对冒号 + 方法名。静态方法的归属者为类名,普通方法的归属者为对象,所以格式为:ClassName::methodName
或 ObjectName::methodName
。
引用静态方法
如果表达式符合“([参数1, 参数2, ...]) -> 类名.静态方法名([参数1, 参数2, ...])”,那么可以简化为“类名::静态方法名”。示例代码:
public class Function2Test {
public static void main(String[] args) {
/**
* 引用静态方法,格式为[类名::方法名]
* 原写法:Example exam = a -> addTenOpreration(a);
* 引用了一个已经实现的 addTenOperation() 方法
*/
Example exam = Function2Test::addTenOperation;
int result = exam.addTenOperation(10);
System.out.println(result);
/**
* 一个参数,无返回值的情况
* 原写法:OneParamButNoReturn opbnr = name -> System.out.println(name);
*/
OneParamButNoReturn opbnr = System.out::println;
opbnr.sayHello("666");
}
@FunctionalInterface
interface Example {
int addTenOperation(int num);
}
public static int addTenOperation(int num) {
// 执行加 10 操作
return num + 10;
}
}
引用对象的方法
如果表达式符合“([参数1, 参数2, ...]) -> 对象名.静态方法名([参数1, 参数2, ...])”,那么可以简化为“对象名::方法名”。示例代码:
public class Function3Test {
public static void main(String[] args) {
Function3Test test3 = new Function3Test();
/**
* 在 Lambda 表达式中使用对象的方法时,[对象名::方法名]
* 原写法:Example2 exam = (num1, num2) -> test3.compare(num1, num2);
*/
Example2 exam = test3::compare;
int result = exam.compareOperation(10, 20);
System.out.println(result);
}
@FunctionalInterface
interface Example2 {
int compareOperation(int num1, int num2);
}
public int compare(Integer o1, Integer o2) {
// 小于--负整数;等于--零;大于--正整数
return o1.compareTo(o2);
}
}
引用构造方法
如果表达式符合“([参数1, 参数2, ...]) -> new 类名([参数1, 参数2, ...])”,那么可以简化为“类名::new”。示例代码:
public class Function4Test {
public static void main(String[] args) {
/**
* 通过[类名::new]的方式来实例化对象
* 原写法:ProductCreator creator = (id, name, price) -> new Product(id, name, price);
*/
ProductCreator creator = Product::new;
Product p1 = creator.getProduct(1, "大棉袄", 365.00);
System.out.println(p1.toString());
}
@FunctionalInterface
interface ProductCreator {
Product getProduct(int id, String name, double price);
}
}
使用情景
以下举例说明一些常用的情景。
创建线程
可以使用 Lambda 表达式来简化线程的创建过程:
new Thread(() -> System.out.println("子线程启动了")).start();
遍历集合
传统遍历一个 List ,基本上都使用 for 循环来遍历,JDK 8 之后 List 有了 forEach() 方法,配合 Lambda 表达式写出更加简洁的方法:
public class Function5Test {
public static void main(String[] args) {
// 1.遍历列表
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5,6,7);
// 使用 Lambda 表达式遍历
list.forEach(System.out::println);
}
}
过滤元素
在 java.util.function
包下有一个断言型接口 Predicate<T>
,其中包含了多种静态 / 默认方法,用于处理复杂的逻辑判断。示范代码:
public class Function5Test {
public static void main(String[] args) {
// 2.过滤元素
List<String> lsit2 = Arrays.asList("Java", "PHP", "C++", "Golang", "Python", "Kotlin");
Predicate<String> condition = (s) -> s.length() > 5;
filter(lsit2, condition);
}
// 使用 Predicate 接口
public static void filter(List<String> list, Predicate condition) {
for (String content : list) {
if (condition.test(content)) {
System.out.println("符合条件的内容:" + content);
}
}
}
}
使用 Lambda 表达式,还可以进一步简化:
public class Function5Test {
public static void main(String[] args) {
// 2.过滤元素
List<String> lsit2 = Arrays.asList("Java", "PHP", "C++", "Golang", "Python", "Kotlin");
// 使用 Lambda 表达式简化
filter(lsit2, (s) -> (((String) s).length() > 5));
}
// 使用 Predicate 接口
public static void filter(List<String> list, Predicate condition) {
// 使用 Lambda 表达式简化
list.stream().filter(condition).forEach((content) -> System.out.println("符合条件的内容:" + content));
}
}
删除元素
ArrayList
类中有一个 removeIf(Predicate<E> filter)
方法可以删除所有满足特定条件的数组元素。
public class Function5Test {
public static void main(String[] args) {
// 3.删除满足条件的元素
List<Product> proList = new ArrayList<Product>() {
{
add(new Product(1, "电动牙刷", 230.00));
add(new Product(2, "品牌文化衫", 99.00));
add(new Product(3, "无线鼠标", 177.00));
add(new Product(4, "机械键盘", 418.00));
add(new Product(5, "除螨仪", 199.00));
}
};
// Predicate<E> filter,使用 Lambda 简化
proList.removeIf(p -> p.getId() == 3);
// 输出验证
System.out.println("删除特定目标后的 ArrayList: " + proList.size());
}
}
元素排序
在 Java 8 之前,通过实现 Comparator
接口,匿名内部类中重写 compare()
方法完成排序,我们现在可以使用 Lambda 表达式来简化代码。
public class Function5Test {
public static void main(String[] args) {
// 4.元素排序
List<Product> proList2 = new ArrayList<Product>() {
{
add(new Product(1, "小米平板5", 2299.00));
add(new Product(2, "米家电动剃须刀", 99.00));
add(new Product(3, "Redmi路由器AX1800", 229.00));
add(new Product(4, "小米中性笔", 24.90));
add(new Product(5, "黑鲨4游戏手机", 2299.00));
add(new Product(6, "米家加湿器", 99.00));
add(new Product(7, "米家指甲刀", 9.90));
add(new Product(8, "Redmi手表", 269.00));
add(new Product(9, "RedmiK40游戏手机", 2299.00));
}
};
// 自定义排序:先比较价格,再比较 id
proList2.sort((s1, s2) -> {
if (s1.getPrice() == s2.getPrice()) {
return Integer.compare(s1.getId(), s2.getId());
} else {
return Double.compare(s1.getPrice(), s2.getPrice());
}
});
// 输出验证
proList2.forEach(System.out::println);
}
}
从逻辑来看,多条件排序就是先判断第一个条件,如果相等,再判断第二个条件,依次类推。在 Java 8 中可以使用 comparing
和一系列 thenComparing
表示多级条件判断(thenComparing
方法是可以有多个的),上面的逻辑可以简化为:
// 使用 Lambda 表达式
proList2.sort(Comparator.comparing(Product::getPrice).thenComparing(Product::getId));
参考
菜鸟教程
极客教程
稀土掘金