Content Table

Lambda 笔记

Lambda 是什么 ?

简单的说,Lambda 就是匿名函数。由于 Java 是完全面向对象的,不能像 C 语言那样独立的定义函数以及函数变量,Java 中的方法 (函数) 只能定义在类或者接口中,所以 Java 采取了一种折衷的方案实现 Lambda: 通过有且只有一个未实现的方法的接口来实现 Lambda,所以这样的接口又叫做 Functional Interface

在 Java 中,Lambda 的本质是匿名内部类的语法糖,用于简化匿名内部类的编码。也许您要问,匿名内部类在编译的时候都会成功 class 文件,但 Lambda 却没有看到对应的 class 文件啊。其实这也好理解,class 文件最后会被 JVM 加载到 byte[] 中,用于生成 Class 对象,Lambda 实现的匿名内部类在运行时生成对应的 bytes[] 来生成 Class 对象就可以了,class 文件只是一个载体,其内容也可以通过 URL 远程加载呢,这样本地是不是也一样看不到 class 文件了呢?

Lambda comes from the Lambda Calculus and refers to anonymous functions in programming.

Lambda 有什么优点 ?

使用 Lambda 表达式可以使代码变的更加简洁紧凑,具体就是简化接口的匿名类的创建,例如下面以创建线程为例演示 Lambda 对代码的简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LambdaTest {
public static void main(String[] args) {
// 未使用 Lambda 创建线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});

// 使用 Lambda 创建线程
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});

t1.start();
t2.start();
}
}

从上面的例子中可以看到,使用 Lambda 简化了代码,但是业务逻辑的部分该怎么写还继续怎么写。

Lambda 怎么使用?

Lambda 使用的接口,首先是一个普通的 Java 接口,使用关键字 interface 定义,但有一个限制条件: 有且仅有一个未实现的方法。

Lambda 表达式的语法格式如下:

1
2
3
4
() -> expression
() ->{ statements; }
(parameters) -> expression
(parameters) ->{ statements; }

Lambda 的用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Foo {
void greeting();
}

public interface Bar {
void fahren(int value);

default void gehen() {
System.out.println("gehen()");
fahren(33);
}

default void fliegen() {
System.out.println("fliegen()");
}
}

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LambdaTest {
public static void main(String[] args) {
Foo foo = () -> {
System.out.println("foo()");
};
foo.greeting();

Bar bar = (value) -> {
System.out.printf("bar(%d)\n", value);
};
bar.fahren(66);
bar.gehen();

arbeiten(() -> {
System.out.println("Lambda Foo as arg");
});
}

public static void arbeiten(Foo foo) {
System.out.println("arbeiten()");
foo.greeting();
}
}

@FunctionalInterface

在定义 用于 Lambda 接口的时候可以使用 @FunctionalInterface 进行限定,当它修饰的对象不满足条件时在编译期就会报错。定义函数式接口时这个注解是可选的,用于在及早发现问题。

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method.

If a type is annotated with this annotation type, compilers are required to generate an error message unless:

  • The type is an interface type and not an annotation type, enum, or class.
  • The annotated type satisfies the requirements of a functional interface.

正确:

1
2
3
4
@FunctionalInterface
interface Foo {
void foo();
}

报错: Foo 不是函数接口, 在接口 Foo 中找到多个抽象方法

1
2
3
4
5
@FunctionalInterface
interface Foo {
void foo();
void bar();
}

Lambda 需要注意什么?

使用 Lambda 的时候,有以下几点需要注意 :

  • 和普通的接口一样,Lambda 使用的接口可以有 0 个或者多个 default 方法
  • Lambda 的接口中未实现的那个方法中不能调用此接口的 default 的方法,但是 default 的方法中可以调用未实现的那个方法
  • Lambda 的接口除了未实现的方法的个数限制,其他方面和普通的接口一样,例如方法可以无参数、有参数、泛型以及Lambda 接口作为方法的参数等
  • Lambda 的接口实现中,不能直接给外部的变量赋值,但可以修改外部变量的属性值

有时候直接修改外部变量是必不可少的,下面以求和为例通过中间层进行解决:

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,这句话几乎概括了计算机软件体系结构的设计要点。

错误的代码:

1
2
3
4
5
6
7
public static void main(String[] args) {
int sum = 0;

Stream.of(1, 2, 3).forEach(n -> {
sum += n; // // Error: Variable used in lambda expression should be final or effectively final
});
}

正确的代码:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
int sum = 0;
int[] result = { 0 }; // 中间层: 缓存结果

Stream.of(1, 2, 3).forEach(n -> {
result[0] += n;
});

sum = result[0];
}

或者:

1
2
3
4
5
6
7
public static void main(String[] args) {
AtomicInteger sum = new AtomicInteger(0);
Stream.of(1, 2, 3, 4).forEach(e -> {
sum.addAndGet(e);
});
System.out.println(sum.get());
}

Java 中典型的 Lambda 接口

下图为 Java 中比较典型的 Lambda 的接口,在 Stream 中大量使用,其中:

  • R (return) 表示返回类型
  • T 和 U 表示参数类型
  • Function 的参数类型和返回类型有可能不一样
  • Operator 的参数类型和返回类型是一样的