Java的Lambda表达式
函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是Lambda演算,所以我们经常把支持函数式编程的编码风格称为Lambda表达式。
在Java程序中,我们经常遇到一大堆单方法接口,即一个接口只定义了一个方法:
- Comparator
- Runnable
- Callable
以Comparator
为例,我们想要调用Arrays.sort()
时,可以传入一个Comparator
实例,以匿名类方式编写如下:
String[] array = ... Arrays.sort(array, new Comparator<String>() { public int compare(String s1, String s2) { return s1.compareTo(s2); } });
上述写法非常繁琐。从Java 8开始,我们可以用Lambda表达式替换单方法接口。改写上述代码如下:
public class Main { public static void main(String[] args) { String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" }; Arrays.sort(array, (s1, s2) -> { return s1.compareTo(s2); }); // 输出:Apple, Banana, Lemon, Orange System.out.println(String.join(", ", array)); } }
观察Lambda表达式的写法,它只需要写出方法定义:
(s1, s2) -> { return s1.compareTo(s2); }
其中,参数是(s1, s2)
,参数类型可以省略,因为编译器可以自动推断出String
类型。-> { ... }
表示方法体,所有代码写在内部即可。返回值的类型也是由编译器自动推断的,这里推断出的返回值是int
,因此,只要返回int
,编译器就不会报错。
并且不用书写class
定义,这样的写法是不是非常简洁。
如果只有一行return xxx
的代码,完全可以用更简单的写法:
Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));
FunctionalInterface
我们把只定义了单方法的接口称之为FunctionalInterface
,用注解@FunctionalInterface
标记。这里以Callable
接口为例:
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
再来看Comparator
接口:
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); default Comparator<T> reversed() { return Collections.reverseOrder(this); } default Comparator<T> thenComparing(Comparator<? super T> other) { ... } ... }
这时候看到Comparator
接口,一些人认为它不应该是定义单方法的接口。
如果你细看,虽然Comparator
接口有很多方法,但只有一个抽象方法int compare(T o1, T o2)
,其他的方法都是default
方法或static
方法。还有boolean equals(Object obj);
是Object
定义的方法,不算在接口方法内。因此,Comparator
也是一个FunctionalInterface
。
看到这有兴趣的还能再补充下
方法引用
除了Lambda表达式,我们还可以直接传入方法引用。例如:
public class Main { public static void main(String[] args) { String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" }; Arrays.sort(array, Main::cmp); System.out.println(String.join(", ", array)); } static int cmp(String s1, String s2) { return s1.compareTo(s2); } }
上述代码在Arrays.sort()
中直接传入了静态方法cmp
的引用,用Main::cmp
表示。
因此,所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。
那什么是方法签名和接口一致呢?
方法参数一致,返回类型相同,我们说两者的方法签名一致。(不看方法名称,也不看类的继承关系)
Comparator<String>
接口定义的方法是int compare(String, String)
,和静态方法int cmp(String, String)
就属于方法签名一致,我们可以直接把方法名作为Lambda表达式传入:
Arrays.sort(array, Main::cmp);
我们再看看如何引用实例方法:
public class Main { public static void main(String[] args) { String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" }; Arrays.sort(array, String::compareTo); System.out.println(String.join(", ", array)); } }
不但可以编译通过,而且运行结果也是一样的,这说明String.compareTo()
方法也符合Lambda定义。
观察String.compareTo()
的方法定义:
public final class String { public int compareTo(String o) { ... } }
有人会疑惑了:这个方法的签名只有一个参数,为什么和int Comparator<String>.compare(String, String)
能匹配呢?
因为实例方法有一个隐含的this
参数,String
类的compareTo()
方法在实际调用的时候,第一个隐含参数总是传入this
,相当于静态方法:
public static int compareTo(this, String o);
所以,String.compareTo()
方法也可作为方法引用传入。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署