1、什么是Lambda?
Lambda是Java8及以上中的一种新的语法,可以理解为是一个函数,而且这个函数是可传递的(即:作为参数)、匿名的(没有函数名),但是这个函数有参数列表、函数主体、返回类型,也有可抛出的异常列表(可选),说到这有没有觉得跟我们经常写的Java方法很像?不同的是我们的Java方法是有名称的,而且是不可传递。Lambda示例如下:
(String s) -> s.length() > 1
(String s):参数列表
s.length() > 1:函数主体
s.length() > 1结果:返回类型(Boolean类型)
public static void main(String[] args) {
test(Arrays.asList("java", "c", "php", "html"), (String s) -> s.length() > 1);
}
public static void test(List<String> strList, Predicate<String> predicate){
for (String s : strList) {
if(predicate.test(s)){
System.out.println(s);
}
}
}
运行结果如下:
java
php
html
扩展:Lambda据说跟数学中的一个λ
符号有点渊源哦,具体自己可查询资料,这里不作详述。
2、Lambda怎么用?
看到上面的例子,你肯定会问那(String s) -> s.length() > 1
和Predicate<String> predicate
是啥关系,完全不搭调呀,这不是乱写么?那么好,接下来我就来解答你问的这个问题,那就是Lambda该用在什么地方?
先说下概念吧,Lambda需要用在函数式接口中,那么什么是函数式接口呢?那就是:只定义一个抽象方法的接口叫做函数式接口。Java API中已经提供了一些函数式接口,比如:
public interface Predicate<T>{
boolean test (T t);
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
public interface Runnable{
void run();
}
...
测验:那下面那些哪个是函数式接口呢?
@FunctionalInterface
public interface PrintNumber {
void printNumber(int a);
}
public interface PrintDouble extends PrintNumber {
void printNumber(double a);
}
public interface NoMethod {
}
答案:只有 PrintNumber 是函数式接口。PrintDouble 不是函数式接口,因为它定义了两个叫作 printNumber 的抽象方法(其中一个是从PrintNumber 那里继承来的)。NoMethod 也不是函数式接口,因为它没有声明抽象方法。
扩展:函数式接口可使用@FunctionalInterface
标注,如上面那个PrintNumber接口一样,当加上这个标注之后编译器会检查这个接口符不符合函数式接口的规则,不符合的话会给出错误提示。(当然这个标注不加也可以,只要你的接口保证了只有一个抽象方法就是函数式接口)。
说到这里,你可能觉得还是有点懵,这废话一大堆都是讲函数式接口,跟Lambda貌似不沾边啊。别着急,下面就给你讲它们之间的关系。
其实,可以这么理解,Lambda表达式其实是函数式接口的实现(就像是接口的实现实例,实现接口中的这个方法具体干的事情),这可能理解也有点绕,说实话我自己都觉得绕,还是用例子来说好了:
Runnable r1 = () -> System.out.println("Hello World");//Lambda实现接口
Runnable r2 = new Runnable(){//匿名类实现接口
public void run(){
System.out.println("Hello World");
}
};
//以上两段代码都有效,说明他们实现的功能是一样的,只是第一段代码比第二段代码简洁了很多
上面描述的只是粗略的概念,下面我们就更进一步,函数式接口和Lambda表达式的共同点,说到这,我们要引入另一个概念,叫做签名,什么是签名,我是这么理解的(也不知道对不对):签名,就是参数和返回结果的结合,也就是输入和输出,读和写,通俗讲就是我给你一块铁,你帮我配成钥匙返回给我。而函数式接口的抽象方法签名可以描述Lambda表达式的签名(意思就是两个签名都是一个意思),就把函数式接口的抽象方法签名称之为函数描述符。下面列举一下Java API中提供的一些函数式接口的函数描述符:
函数式接口 | 函数描述符 |
---|---|
Predicate | T->boolean |
Consumer | T->void |
Function | T->R |
Runnable | ()->void |
下一步,我再介绍一下Lambda的类型检查、类型推断:
当我们第一次提到Lambda表达式时,说它可以为函数式接口生成一个实例。然而,Lambda表达式本身并不包含它在实现哪个函数式接口的信息。为了全面了解Lambda表达式,你应该知道Lambda的实际类型是什么。
类型检查:
Lambda的类型是从使用Lambda的上下文推断出来的。上下文中Lambda表达式需要的类型称为目标类型。让我们通过一个
例子,看看当你使用Lambda表达式时背后发生了什么。
printStr(Arrays.asList("java", "c", "php", "html"), (String s) -> s.length() > 1);
public static void printStr(List<String> strList, Predicate<String> predicate){
}
类型推断
你还可以进一步简化你的代码。Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。换句话说,Java编译器会像下面这样推断Lambda的参数类型:
(String s) -> s.length() > 1);
s ->s.length() > 1;
请注意,有时候显式写出类型更易读,有时候去掉它们更易读。没有什么法则说哪种更好;对于如何让代码更易读,程序员必须做出自己的选择。