作者:曹伟,叩丁狼高级讲师。原创文章,转载请注明出处。
Lambda表达式语法揭秘
语法分析
我们先从之前写的简单的开启线程的例子开始分析:
传统写法:
@Test
public void testTradition() throws Exception {
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("使用传统方式开启线程");
}
}).start();
}
使用Lambda表达式
@Test
public void testLambda() throws Exception {
new Thread(()->System.out.println("使用Lambda方式开启线程")).start();;
}
在上述代码中,Threan类的构造器中需要传入一个Runnable接口的实现类,而Runnable接口中只有一个run方法(实现类中需要实现该方法并将任务代码写该方法中)
那既然我们知道Thread需要一个Runnable,并且只需要重写一个run方法,并且run方法中就是我们要写的任务代码,那我们为何不直接把我们要完成的任务代码传递给Thread呢?
所以我们就可以规定一种语法,将我们要完成的任务直接传递给Thread:
()->System.***out***.println("使用Lambda方式开启线程")
这种新的语法就是:“->”
“->”被称为Lambda 操作符或箭头操作符。
它将Lambda 分为两个部分:
左侧:指定了Lambda 表达式需要的所有参数,run方法不需要参数,所以可以直接写()
右侧:指定了Lambda 体,即Lambda 表达式要执行的任务、功能或者说是行为。
我们要完成的功能就是在run方法中输出一句话,所以直接写
System.***out***.println("使用Lambda方式开启线程")
可传递的匿名函数
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型。这个定义还是太模糊的,让我们从以下几个关键词慢慢道来。
●匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而做得多!
●函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型。
●传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
●简洁——无需像匿名类那样写很多模板代码。
那到这里我们已经清楚了Lambda表达式的基本语法格式,那再继续思考一个问题:是不是所有的接口类型作为参数传递(传统写法就是匿名内部类)都可以使用Lambda表达式呢?
答案是不可以!因为只有接口中只有一个抽象方法的时候,Lambda表达式才可以正确的“猜测”到你传递的任务实际上是在实现接口中的唯一的那一个抽象方法!
那么我们把这种只有一个抽象方法的接口称作为“函数式接口”,只有函数式接口才可以使用Lambda表达式进行函数式编程!
函数式接口-@FunctionalInterface
只要确保接口中有且仅有一个抽象方法即可
格式:
修饰符 interface 接口名称 {
[public abstract] 返回值类型 方法名称(可选参数信息);
// 其他
}
为了便于区分并便于编译器进行语法校验,JDK8中还引入了一个新的注解:
@FunctionalInterface
该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
需要注意的是,即使不使用该注解,只要满足函数式接口的定义,该接口仍然是一个函数式接口,使用起来都一样。(可以类比下@Override 注解的作用)
我们可以去看下Runnable接口的源码,发现它就是一个函数式接口
那么我们也可以来定义一个简单的函数式接口:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
那到这里我们已经知道了只要参数是函数式接口我们就可以使用Lambda表达式讲我们要完成的任务作为参数直接传递,但是接口中的方法是否有参数,有几个参数,是否有返回值情况可分为好多种呢!那我们就来具体看一下吧!
语法详解
以下是lambda表达式的语法特征:
1.可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
2.可选的参数圆括号():一个参数无需定义圆括号,但多个参数需要定义圆括号()。
3.可选的大括号{}:如果主体包含了一个语句,就不需要使用{}。
4.可选的返回关键字return:如果主体只有一个表达式返回值则可以省略return和{}
public class LambdaSyntax {
@Test
public void test1() throws Exception {
// 以下是lambda表达式的语法特征:
// 1.可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
Arrays.asList("a", "b", "c").forEach((String x) -> System.out.print(x));// 声明了参数类型
Arrays.asList("a", "b", "c").forEach((x) -> System.out.print(x));// 省略了参数类型
// 2.可选的参数圆括号():一个参数无需定义圆括号,但没有参数或多个参数需要定义圆括号()。
Arrays.asList("a", "b", "c").forEach(x -> System.out.print(x));// 一个参数可以省略()
new Thread(() -> System.out.println("没有参数")).start();// 没有参数不可以省略()
Arrays.asList("a", "b", "c").stream().sorted((x, y) -> y.compareTo(x)).forEach(x -> System.out.print(x));// 多个参数不可以省略()
// 3.可选的大括号{}:如果主体包含了一个语句,就不需要使用{}。
Arrays.asList("a", "b", "c").stream().sorted((x, y) -> {
System.out.println("Lambda中有多条语句,return和{}不可以省略");
return y.compareTo(x);
}).forEach(x -> System.out.print(x));// 多条语句不可以省略{}
Arrays.asList("a", "b", "c").stream().sorted((x, y) -> y.compareTo(x)).forEach(x -> System.out.print(x));// 一条语句可以省略{}
// 4.可选的返回关键字return:如果主体只有一个表达式返回值则可以省略return和{}
Arrays.asList("a", "b", "c").stream().sorted((x, y) -> {
System.out.println("Lambda中有多条语句,return和{}不可以省略");
return y.compareTo(x);
}).forEach(x -> System.out.print(x));// 多条语句不可以省略{}
Arrays.asList("a", "b", "c").stream().sorted((x, y) -> y.compareTo(x)).forEach(x -> System.out.print(x));// 省略{}和return
}
}
●总结
Lambda表达式极大的丰富了Java语言的表现力,简化了Java代码的编写,但是语法细节我们或许很难记住,那么只需要记住一条:可推断则可省略!
●注意
在某些情况下,类型信息可以帮助阅读者理解代码,这时需要手动声明类型,让代码便于阅读。有时省略类型信息更加简洁明了,还可以减少干扰,让阅读者关注业务逻辑,这时就可以省略;所以在开发中建议大家根据自己或项目组的习惯结合实际情况,进行类型声明或省略。