土味情话:“你在干嘛”“我也想你”
前言
Lambda是一个匿名函数,即没有函数名的函数。 Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)
是不是一脸懵逼,语言上的解释是苍白无力的,并且每个人对于lambda都有自己的理解,所以只有通过实践就会明白到底是怎么一回事
来吧,展示~
前戏
先来看看如果不使用Lambda,匿名函数怎么写
-
比较器对集合排序—Java8之前写法
- 准备数据
class Employee { private String name; private Integer age; public Employee(String name, Integer age) { this.name = name; this.age = age; } // 需重写toString() } List<Employee> list = Arrays.asList( new Employee( "张三", 18), new Employee( "李四", 28), new Employee( "王五", 8), new Employee( "赵六", 38), new Employee( "田七", 58) );
- 示例代码
@Test public void test() { // 定义比较器,通过Employee对象的age字段进行升序排列 Comparator<Employee> comparator = new Comparator<Employee>() { @Override public int compare(Employee o1, Employee o2) { // 如果返回值 <1 则升序, >1 则降序, =0则认为相等 return o1.age - o2.age; } }; // 对list集合数据进行排序 [1] list.sort(comparator); // 打印排序之后的list [2] for (Employee item : list) { System.out.println(item); } }
以上的代码则为Java8之前的示例代码,一共需要10行代码来进行排序
其中
[1]
,[2]
标识的地方均是可以使用Lambda来优化 -
比较器对集合排序—Lambda
- 示例代码
@Test public void test1() { // 根据age的大小进行升序排列 list.sort((o1, o2) -> { return o1.age - o2.age; }); // 打印输出 list.forEach(item -> System.out.println(item)); }
Lambda表达式,一共就只需要4行代码,简洁不简洁,优雅不优雅(还可以更简洁,更优雅)
- 具体含义
(o1, o2) -> {return o1.age - o2.age}
这个就是Lambda表达式,相当于上面的Comparator
接口中的compare
方法。- 第一种方法使用匿名内部类,并声明重写的方法,然后处理逻辑
- Lambda使用箭头函数,直接重写逻辑,不需要声明类名、方法名称、参数类型。JVM会自动进行推断重写的方法名、该参数的类型。
因为方法名和类型都是自动推断的,所以在定义箭头函数(Lambda)体的时候,定义的变量只能使用该类型所具有的方法,并且定义的变量的类型也要和指定的类型相同
-
类型推断说明
-
示例代码中
List<Employee>
中保存的是Employee
对象,所以在(o1, o2) -> o1.age - o2.age
中,o1
和o2
的类型为Employee
,所以在函数体中调用方法时,只能调用Employee
具有的方法,并且返回的类型也必须是int
类型,因为Lambda相当于重写的是Comparator的compare
方法,该方法的返回值为int
,相当于隐式声明类型。 -
那可以随便返回和传递参数类型吗?
自然是不可以的,因为在
List
的sort
方法中,第二个参数必须是Comparator
接口的实现类,所以参数的类型和返回值类型必须和Comaparator
中的compare
方法保持一致// @FunctionalInterface 标示为函数式接口 @FunctionalInterface public interface Comparator<T> { // 定义了方法的返回值,参数类型 int compare(T o1, T o2); }
所以,并不是没有类型,而是通过约定来将类型的声明省略,但是在运行的时候会自动推断类型,保证程序的正确性。
@FunctionalInterface 标示为函数式接口,函数式接口指定该接口只能定义一个抽象方法。所以在Lambda中不生命方法名称时,不用担心到底调用的是哪个方法,因为只有一个抽象方法。
后面的文章会再详细说明函数式接口
-
-
创建线程—Java8之前
@Test public void test() { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("创建线程"); } }, "Thread-1"); thread.start(); }
创建
Thread
对象,并重写里面的Runnable
的run
方法,调用调用start
方法开启线程 -
创建线程—Lambda
@Test public void test() { // 创建Runnable接口的实现类,使用箭头函数表示 // 可以理解为多态的形式,接口 = 实现类 Runnable runnable = () -> System.out.println("创建线程"); // 传递接口实现类对象 Thread thread = new Thread(runnable); thread.run(); }
接口 = 实现类
这种方式就是多态的应用呀,Runnable
就是一个函数式接口,所以该接口只有一个public abstract void run();
抽象方法,这里的Lambda表达式定义的就是run
方法,并且run
方法没有参数,也没有返回值
正戏
通过前戏,基本上已经进入状态了,下面可以进入正戏了~~
-
自定义接口使用Lambda
interface Multi { String twoArg(String head, Double d); } interface FileOperator { void createFile(String path); } @Test public void test() { // 定义Multi接口,并通过Lambda实现twoArg方法,如果只有一行代码,可以不用定义{} 和 声明 return,默认 return 返回值 Multi multi = (h, d) -> h + d; // 调用接口的方法 String str = multi.twoArg("Pi!", 3.1415926); System.out.println(str); } @Test public void test() { // 定义lambda表达式,变量p的类型为String FileOperator op = (p) -> { File file = new File(p); System.out.println(file.getName()); }; op.createFile("/tmp/file.txt"); }
这样就可以自定义接口,并通过Lambda来动态实现接口方法。
不过一般来讲需要使用Lambda来动态实现的接口,在接口上面加上
@FunctionalInterface
,这个注解用来表示该接口是一个函数式接口。
小总结
- 匿名内部类的方式,在Java8中可以直接使用Lambda实现,更为简洁直观优雅
- 自定义Lambda处理逻辑可以用来做函数式接口的实现,实现特定的功能,并且根据不同的实现,自定义不同的Lambda的处理逻辑(多态有点像)
- 注意Lambda表达式中虽然没有显式指定参数和返回值等类型,不过必须遵守具体接口的参数列表类型和返回值类型
彩蛋
上面说到排序的代码还可以更简洁~
@Test
public void test1() {
// 根据age的大小进行升序排列
list.sort((o1, o2) -> o1.age - o2.age);
// 打印输出
list.forEach(System.out::println);
}
2行代码,是不是更优雅。
第一行:在方法体中只有一行代码的情况下,可以省略{}
,并且可以return
也可以省略,默认会将计算的结果返回
第二行:forEach
中可以使用Lambda的箭头函数,也可以使用方法引用(和Lambda基本一致,在特定的情况下可以替换Lambda)
微信公众号「指尖上的代码」,欢迎关注~
你的点赞和关注是写文章最大的动力~