1. 前言
谈起这个Lambda表达式,我不禁想起了我的一个小故事,没有它,我估计到现在都还不知道何为Lambda表达式。
故事是这样的:在大三上时,我面临着考研Or就业的抉择,于是,我去找了一位我校的老师,与他进行简单地沟通了一下,他对我的情况作出了简要分析,我也思索了良久。最后,我选择了就业-----Java开发工程师。于是,他说:你想搞Java开发的话,Java基础要好。当时,我自我感觉良好,就跟他谦虚地说:我觉得我的Java基础还不错。然后,他给我来了一句:你知道Lambda表达式(当时,我并不知道是这个东西)吗?我有点懵,当时脑海中浮现的是数学中的“λ”,但这跟Java毫无血缘关系啊。我说:我没听说它。听后,那位老师哈哈大笑。
过后,我到菜鸟教程网站上了解了一下:Java 8 Lambda 表达式。所以,这里简单地写写博客记录一下。
2. 认识Lambda
2.1 Lambda简介
Lambda 表达式(又名闭包)是 JDK8 的一个新特性,它:
- 允许把函数作为一个方法的参数(函数作为参数传递进方法中)
- 可以取代大部分的匿名内部类
- 更容易操作集合
Lambda表达式的写法(上述说法可能比较抽象,下文会有案例)可以极大地优化代码结构,使Java代码看起来更优雅。
2.2 Lambda语法
Lambda表达式有三个部分:
- 参数列表 ()
- 箭头 ->
- 主体 {}
语法格式:
(parameters) -> expression
或
(parameters) ->{ statements; }
语法格式简要说明:
- 类型声明(可选):不需要声明参数类型,编译器可以统一识别参数值
- 参数圆括号(可选):一个参数无需定义圆括号,但多个参数需要定义圆括号
- 大括号(可选):如果主体包含了一条语句,就不需要使用大括号
- 返回关键字(可选):如果主体只有一个表达式返回值。则编译器会自动返回值,大括号需要指定明表达式返回了一个数值
2.3 Lambda表达式实例
下面是一些lambad表达式的简单例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
2.4 自定义一个Lambda表达式
接下来自定义一个Lambda表达式,通过这个过程,可以进一步了解Lambda表达式。
对于一个Java变量,我们可以给其赋值:
如果你想把“一块代码”(函数)赋给一个Java变量,那你应该怎么做呢?比如:我想把右边一个函数doTest()赋值给一个Java变量aBlockOfCode
在Java8之前是做不到的。但在Java8问世之后,可以通过Lambda表达式做到
按照Lambad表达式的语法格式进行简化,带代码看起来更优雅
其实,还可以优化,去掉参数的小括号(只有一个参数)
就这样,我们成功地把一个函数赋给了一个Java变量。
但有一个问题:这个变量aBlockOfCode是什么数据类型呢?
在Java8中,所有的Lambda的数据类型都是一个接口(很特殊),而Lambda表达式本身就是接口的实现。这样说,或许比较抽象,接下来继续看例子。
我们给上面的Java变量aBlockOfCode添加一个数据类型(接口)
这种只有一个接口函数(抽象函数)需要被实现的接口类型,我们叫它“函数式接口(下文会讲)”。为了避免他人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个注解@FunctionalInterface, 这样别人就无法在里面添加新的接口函数(抽象函数)了:
所以,一个完整的Lambda表达式声明:
2.5 Lambda表达式对接口的要求
2.5.1 函数式接口
虽然使用Lambda表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用Lambda表达式来实现。Lambda规定:接口中只能有一个需要被实现的方法(抽象方法),但可以有多个非抽象方法(静态方法、默认方法)-----函数式接口。
JDK 1.8 之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
- …
JDK也新增了一些函数式接口。
2.5.2 注解@FunctionalInterface
@FunctionalInterface修饰函数式接口的,它要求接口中的抽象方法只有一个。此注解往往会和lambda表达式一起出现。接下来举一个例子:
2.5.3 举个例子
定义了一个函数式接口GreetingService 如下:
@FunctionalInterface
interface GreetingService {
void sayMessage(String message);
}
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
GreetingService greetService = message -> System.out.println("Hello " + message);
3. 使用Lambda表达式
3.1 将Lambda作为方法的参数,传进方法中
函数式接口MyLambdaInterface
@FunctionalInterface
public interface MyLambdaInterface {
void doTest(String s);
}
Test
1.使用lambda表达式
public class Test {
public static void test(MyLambdaInterface myLambdaInterface, String s) {
myLambdaInterface.doTest(s);
}
public static void main(String[] args) {
// 直接将lambad表达式传给了test()方法
test(s -> {System.out.println(s);}, "Hello World");
}
}
2.使用传统方法
先定义一个函数式接口的实现类MyLambdaInterfaceImpl,并重写方法doTest()
MyLambdaInterfaceImpl
public class MyLambdaInterfaceImpl implements MyLambdaInterface {
@Override
public void doTest(String s) {
System.out.println(s);
}
}
再实例化MyLambdaInterfaceImpl 对象,并传入test()方法中
Test
public class Test {
public static void test(MyLambdaInterface myLambdaInterface, String s) {
myLambdaInterface.doTest(s);
}
public static void main(String[] args) {
// 此处也可以用匿名内部类实现
test(new MyLambdaInterfaceImpl(), "Hello World");
}
}
从代码的简洁度来说,lambda表达式比传统的方法更简洁、优雅
3.2 Lambda表达式替换匿名内部类
3.2.1 传统方法创建线程
创建线程的方法很多,我知道的有四个:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 通过线程池创建
这里创建线程就使用Runnable接口
3.2.1.1 使用外部类创建线程
自定义一个类并实现Runnable接口,重写run()方法。
OuterClass
public class OuterClass implements Runnable {
@Override
public void run() {
// 打印当前线程名称
System.out.println(Thread.currentThread().getName());
}
}
Test
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new OuterClass()).start();
}
}
}
创建两个线程,并打印线程的名称
3.2.1.2 使用匿名内部类创建线程
Test
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
}
3.2.2 Lambda表达式创建线程
Test
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}).start();
}
}
}
3.3 Lambda表达式操作集合
因为操作集合的操作类型比较多,这里就简要举2个简单的例子
3.3.1 使用Lambda表达式遍历集合
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6);
list.forEach(
e -> {
if (e % 2 == 0) {
System.out.println(e);
}
});
}
}
3.3.2 map()
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6);
List<Integer> collect = list.stream().map(e -> e * e).collect(Collectors.toList());
System.out.println(collect);
}
4. Lambda表达式中的闭包问题
此处的闭包问题指的是 变量作用域的问题,这种问题也会在匿名/局部内部类中存在(Java中的内部类了解一下)。lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
如:
MyLambdaInterface接口上面有。
也可以直接在 lambda 表达式中访问外层的局部变量:
public class Test {
public static void main(String[] args) {
String name = "zzc";
MyLambdaInterface myLambdaInterface = s -> {
System.out.println(s + name);
};
myLambdaInterface.doTest("hello ");
}
}
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义),否则,会报错
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
好了,今天的Lambda表达式跟大家分享到这儿了,希望大家能懂!~
【参考资料】
Lambda 表达式有何用处?如何使用?
菜鸟教程