---
category:
  - Java核心
tag:
  - Java
---

# 深入浅出 Java 8 Lambda表达式


今天分享的主题是《Lambda 表达式入门》，这也是之前一些读者留言强烈要求我写一写的，不好意思，让你们久等了，现在来满足你们，为时不晚吧？

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/java8/Lambda-1.jpg)

### 01、初识 Lambda

Lambda 表达式描述了一个代码块（或者叫匿名方法），可以将其作为参数传递给构造方法或者普通方法以便后续执行。考虑下面这段代码：

```java
() -> System.out.println("沉默王二")
```

来从左到右解释一下，`()` 为 Lambda 表达式的参数列表（本例中没有参数），`->` 标识这串代码为 Lambda 表达式（也就是说，看到 `->` 就知道这是 Lambda），`System.out.println("沉默王二")` 为要执行的代码，即将“沉默王二”打印到标准输出流。

有点 Java 基础的同学应该不会对 Runnable 接口感到陌生，这是多线程的一个基础接口，它的定义如下：

```java
@FunctionalInterface
public interface Runnable
{
   public abstract void run();
}
```

Runnable 接口非常简单，仅有一个抽象方法 `run()`；细心的同学会发现一个陌生的注解 `@FunctionalInterface`，这个注解是什么意思呢？

我看了它的源码，里面有这样一段注释：

>Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

大致的意思就是说，通过 `@FunctionalInterface` 标记的接口可以通过 Lambda 表达式创建实例。具体怎么表现呢？

原来我们创建一个线程并启动它是这样的：

```java
public class LamadaTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("沉默王二");
            }
        }).start();
    }
}
```

通过 Lambda 表达式呢？只需要下面这样：

```java
public class LamadaTest {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("沉默王二")).start();
    }
}
```

是不是很妙！比起匿名内部类，Lambda 表达式不仅易于理解，更大大简化了必须编写的代码数量。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/java8/Lambda-2.jpg)

### 02、Lambda 语法

每个 Lambda 表达式都遵循以下法则：

```
( parameter-list ) -> { expression-or-statements }
```

`()` 中的 `parameter-list` 是以逗号分隔的参数。你可以指定参数的类型，也可以不指定（编译器会根据上下文进行推断）。Java 11 后，还可以使用 `var` 关键字作为参数类型，有点 JavaScript 的味道。

`->` 相当于 Lambda 的标识符，就好像见到圣旨就见到了皇上。

`{}` 中的 `expression-or-statements` 为 Lambda 的主体，可以是一行语句，也可以多行。

可以通过 Lambda 表达式干很多事情，比如说

1）为变量赋值，示例如下：

```java
Runnable r = () -> { System.out.println("沉默王二"); };
r.run();
```

2）作为 return 结果，示例如下：

```java
static FileFilter getFilter(String ext)
{
    return (pathname) -> pathname.toString().endsWith(ext);
}
```

3）作为数组元素，示例如下：

```java
final PathMatcher matchers[] =
{
        (path) -> path.toString().endsWith("txt"),
        (path) -> path.toString().endsWith("java")
};
```

4）作为普通方法或者构造方法的参数，示例如下：

```java
new Thread(() -> System.out.println("沉默王二")).start();
```

需要注意 Lambda 表达式的作用域范围。

```java
public static void main(String[] args) {

    int limit = 10;
    Runnable r = () -> {
        int limit = 5;
        for (int i = 0; i < limit; i++)
            System.out.println(i);
    };
}
```

上面这段代码在编译的时候会提示错误：变量 limit 已经定义过了。

和匿名内部类一样，不要在 Lambda 表达式主体内对方法内的局部变量进行修改，否则编译也不会通过：Lambda 表达式中使用的变量必须是 final 的。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/java8/Lambda-3.jpg)
这个问题发生的原因是因为 Java 规范中是这样规定的：

>Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression
must either be declared final or be effectively final [(§4.12.4)](http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12.4),
or a compile-time error occurs where the use is attempted.

大致的意思就是说，Lambda 表达式中要用到的，但又未在 Lambda 表达式中声明的变量，必须声明为 final 或者是 effectively final，否则就会出现编译错误。

关于 final 和 effectively final 的区别，可能有些小伙伴不太清楚，这里多说两句。

```java
final int a;
a = 1;
// a = 2;
// 由于 a 是 final 的，所以不能被重新赋值

int b;
b = 1;
// b 此后再未更改
// b 就是 effectively final

int c;
c = 1;
// c 先被赋值为 1，随后又被重新赋值为 2
c = 2;
// c 就不是 effectively final
```

明白了 final 和 effectively final 的区别后，我们了解到，如果把 limit 定义为 final，那就无法在 Lambda 表达式中修改变量的值。那有什么好的解决办法呢？既能让编译器不发出警告，又能修改变量的值。

思前想后，试来试去，我终于找到了 3 个可行的解决方案：

1）把 limit 变量声明为 static。

2）把 limit 变量声明为 AtomicInteger。

3）使用数组。

下面我们来详细地一一介绍下。

#### 01）把 limit 变量声明为 static

要想把 limit 变量声明为 static，就必须将 limit 变量放在 `main()` 方法外部，因为 `main()` 方法本身是 static 的。完整的代码示例如下所示。

```java
public class ModifyVariable2StaticInsideLambda {
    static int limit = 10;
    public static void main(String[] args) {
        Runnable r = () -> {
            limit = 5;
            for (int i = 0; i < limit; i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}
```

来看一下程序输出的结果：

```
0
1
2
3
4
```

OK，该方案是可行的。

#### 02）把 limit 变量声明为 AtomicInteger

AtomicInteger 可以确保 int 值的修改是原子性的，可以使用 `set()` 方法设置一个新的 int 值，`get()` 方法获取当前的 int 值。

```java
public class ModifyVariable2AtomicInsideLambda {
    public static void main(String[] args) {
        final AtomicInteger limit = new AtomicInteger(10);
        Runnable r = () -> {
            limit.set(5);
            for (int i = 0; i < limit.get(); i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}
```

来看一下程序输出的结果：

```
0
1
2
3
4
```

OK，该方案也是可行的。

#### 03）使用数组

使用数组的方式略带一些欺骗的性质，在声明数组的时候设置为 final，但更改 int 的值时却修改的是数组的一个元素。

```java
public class ModifyVariable2ArrayInsideLambda {
    public static void main(String[] args) {
        final int [] limits = {10};
        Runnable r = () -> {
            limits[0] = 5;
            for (int i = 0; i < limits[0]; i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}
```

来看一下程序输出的结果：

```
0
1
2
3
4
```

OK，该方案也是可行的。

### 03、Lambda 和 this 关键字

Lambda 表达式并不会引入新的作用域，这一点和匿名内部类是不同的。也就是说，Lambda 表达式主体内使用的 this 关键字和其所在的类实例相同。

来看下面这个示例。

```java
public class LamadaTest {
    public static void main(String[] args) {
        new LamadaTest().work();
    }

    public void work() {
        System.out.printf("this = %s%n", this);

        Runnable r = new Runnable()
        {
            @Override
            public void run()
            {
                System.out.printf("this = %s%n", this);
            }
        };
        new Thread(r).start();
        new Thread(() -> System.out.printf("this = %s%n", this)).start();
    }
}
```

Tips：`%s` 代表当前位置输出字符串，`%n` 代表换行符，也可以使用 `\n` 代替，但 `%n` 是跨平台的。

`work()` 方法中的代码可以分为 3 个部分：

1）单独的 this 关键字

```java
System.out.printf("this = %s%n", this);
```

其中 this 为 `main()` 方法中通过 new 关键字创建的 LamadaTest 对象——`new LamadaTest()`。

2）匿名内部类中的 this 关键字

```java
Runnable r = new Runnable()
{
    @Override
    public void run()
    {
        System.out.printf("this = %s%n", this);
    }
};
```

其中 this 为 `work()` 方法中通过 new 关键字创建的 Runnable 对象——`new Runnable(){...}`。

3）Lambda 表达式中的 this 关键字

其中 this 关键字和 1）中的相同。

我们来看一下程序的输出结果：

```java
this = com.cmower.java_demo.journal.LamadaTest@3feba861
this = com.cmower.java_demo.journal.LamadaTest$1@64f033cb
this = com.cmower.java_demo.journal.LamadaTest@3feba861
```

符合我们分析的预期。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/java8/Lambda-4.jpg)

### 04、最后

尽管 Lambda 表达式在简化 Java 编程方面做了很多令人惊讶的努力，但在某些情况下，不当的使用仍然会导致不必要的混乱，大家伙慎用。

好了，我亲爱的读者朋友们，以上就是本文的全部内容了。能在疫情期间坚持看技术文，二哥必须要伸出大拇指为你点个赞👍。原创不易，如果觉得有点用的话，请不要吝啬你手中**点赞**的权力——因为这将是我写作的最强动力。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)