Lambda.md 9.9 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
---
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)