前言
Lambda表达式是Java8的一个特性,而Java8是2014发布的,那时候我刚上大学,还没认识Java呢。过了很长一段时间才学习了Lambda表达式,但是一直没怎么用过,已经忘了差不多了,所以现在特地复习一下。
学一个东西我们得知道它是什么,有什么用,怎么用。
是什么
从不同角度上来说,Lambda表达式是一个语法糖,也可以说是匿名函数。
语法糖
何谓语法糖,我的理解是语法糖就是方便程序员开发的一个功能的简化语法。举个栗子,在Java5中出现的增强for循环:foreach,我们一般是这么使用的
List<Integer> nums = new ArrayList<>();
for (Integer n : nums) { System.out.println(n); }
那其实反编译以后是下面这样子的
List nums = new ArrayList();
Integer n;
for(Iterator iterator = nums.iterator(); iterator.hasNext(); System.out.println(n))
n = (Integer)iterator.next();
如果让我们写起来麻烦吧,我们用foreach语法完成了迭代器迭代的功能,但却写了更少的代码,这就是语法糖的作用。至于其中还有类型擦除,想要了解的可以去这里看看详解Java泛型(二)之类型擦除
匿名函数
匿名内部类我们知道,一个没有名字的内部类。像使用Runable
的时候我们不想在另外定义一个类去实现它,就可以像下面这样子做
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Thread start...");
}
};
而匿名函数呢,故名思议就是一个没有名字的函数。还是以Runable
为例,我连run
方法都不想定义了
Runnable r = () -> System.out.println("Thread start...");
这就是Lambda表达式的一个例子,先不用去管为什么可以是这样子,下面会具体说到怎么使用。
有什么用
Lambda表达式是什么我们搞清楚了,有什么用也就容易理解了。既然它是一个语法糖,那么就是用来简化开发的,通过上面的Runable
例子大家就可以体会到。
怎么用
怎么用其实就是学习它的语法。在学习Lambda语法前得说一下函数式接口,因为Lambda表达式需要这个鸟玩意。就像是我们要实现一个接口,就必须先得有那个接口。
函数式接口
所谓函数式接口,其实就是只包含一个抽象方法的接口。像上面栗子中的Runable
接口,只有run
方法,这就是典型的函数式接口。
@FunctionalInterface
public interface Runnable
在它的接口定义上,有那么一个注解@FunctionalInterface
来表示这是一个函数式接口。这个@FunctionalInterface
注解的作用就是当你定义了一个接口,但是这个接口不符合函数式接口的规定即只能有一个抽象方法,则编译时会报错。
那为什么Lambda表达式需要这么一个东西支持呢?因为Lambda表达式的出现就是为了能够方便、快捷地创建出这种函数式接口的实例呀!!
有没有发现很多函数式接口的创建都很麻烦,比如说Runnable
又或者说Comparator
,要么要定义一个类去实现他们,又或者要用匿名内部类,但是当我只有一句代码就能实现功能的时候却要用很多其它无用的代码去支持。像上面Runnable
的栗子我只是需要打印一句话而已。
函数式接口实例的创建就是一个问题,Lambda表达式就是一个解决方案。
语法规则
Java8中引入了一个新的操作符“->” ,它将 Lambda表达式分为两个部分:
- 在其左边:表示函数的参数
- 在其右边:表示要执行的代码(称作Lambda体)
下面就来具体看看怎么用
语法一
无参数,无返回值
Runnable
接口的run
方法就是一个无参数无返回值的方法,那么就可以这样子使用Lambda表达式来创建Runnable
的实例
Runnable r = () -> System.out.println("Thread start...");
new Thread(r).start();
语法二
有一个参数,并且无返回值
Consumer
接口的accept
方法就是一个有一个参数并且无返回值的方法,那么就可以这样子使用Lambda表达式来创建Consumer
的实例
Consumer<Integer> consumer = (x) -> System.out.println(x);
consumer.accept(5);
上面的例子中Consumer
参数类型为Integer
语法变形
其实,只需要一个参数的时候,”->”左边的括号可以省略,如
Consumer<Integer> consumer = x -> System.out.println(x);
consumer.accept(5);
还需要啰嗦一句的是Consumer
接口是Java8内置的函数式接口,你想想呀,有时候你要用Lambda表达式还得自己先定义一个函数式接口,那该多麻烦呀。所以下面用到的例子的接口都是Java8内置的函数式接口。Java8内置的函数式接口有很多,你总能找到一个能满足你的。
语法三
有一个参数,并且有返回值
Function
接口的apply
方法就是一个有一个参数并且有返回值的方法,那么就可以这样子使用Lambda表达式来创建Function
的实例
Function<String, Integer> f = s -> {
int result = Integer.parseInt(s);
return result;
};
Integer num = f.apply("2333");
其中,参数是String
类型的,返回值是Integer
类型的,上面代码的功能就是创建一个Function实例,它的apply方法功能是接受一个String
类型的参数将其转换成Integer
类型。
需要注意的是,当”->”右边需要执行的代码有多条的时候,需要用大括号括起来。
语法变形
当”->”右边需要执行的代码只有一条语句,return和大括号都可以省略不写,如
Function<String, Integer> f = s -> Integer.parseInt(s);
Integer num = f.apply("2333");
其它语法
多个参数的情况
在上面的语法中,已经展示了有一个参数的情况,那以此类推,有多个参数的情况其实就是加多几个参数咯
BiFunction
接口的apply
方法就是一个有两个参数并且有返回值的方法
BiFunction<Integer, Integer, String> b = (x,y) -> x+y+"";
String str = b.apply(1, 2);
上面这段代码的功能是创建一个BiFunction
接口实例,其apply
方法的功能是接收两个Integer
类型的参数让它们相加然后转成String
返回。
加上参数类型
上面的例子,在”->”左边的参数都没有声明它们的类型,那其实是可以声明的,比如
BiFunction<Integer, Integer, String> b = (Integer x, Integer y) -> x+y+"";
String str = b.apply(1, 2);
不过我们一般都不去声明,麻烦。反正编译器很聪明,可以帮我们推断出参数类型。以前最经常见到的类型推断不就这样子的嘛List<String> list = new ArrayList<>();
ArrayList
的尖括号不用填类型,编译器也能推断出来是String
。