Java lambda表达式

语法

(参数) -> {表达式} ,形如:

1
(String first, String second) -> first.length() - second.length()

函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。

lambda表达式可以看做一个函数,并且lambda表达式可以传递到函数式接口。如下的Person接口:

1
2
3
interface Person {
public void run(String name);
}

可以使用lambda表达式实现接口的方法:

1
Person person = name -> System.out.println(name + " is running.");

方法引用

上述lambda表达式也可以传递一个方法的引用:

1
Person person = System.out::println;

有三种方法引用的情况:

  • object::instanceMethod

传递一个对象的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.demo.test;

public class LambdaTest {

public static void main(String[] args) {
Woman woman = new Woman();
Person person = woman::run;
person.run("huihui");
}
}

interface Person {

public void run(String name);
}

class Woman {

public void run(String name) {
System.out.println(name + "在为减肥跑步。");
}
}

运行结果:

1
huihui在为减肥跑步。
  • Class::staticMethod

传递一个类的静态方法。

1
Person person = System.out::println;
  • Class::instanceMethod

传递一个类的实例方法。第一个参数会成为方法的目标,例如,String::compareToIgnoreCase等同于(x, y) -> x.compareToIgnoreCase(y)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.demo.test;

public class LambdaTest {

public static void main(String[] args) {
Person person = Woman::run;
Woman woman = new Woman();
person.run(woman,"huihui");
}
}

interface Person {

public void run(Woman woman, String name);
}

class Woman {

public void run(String name) {
System.out.println(name + "在为减肥跑步。");
}
}

运行结果:

1
huihui在为减肥跑步。

方法引用中,使用this或者super也是合法的。

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
package com.example.demo.test;

public class LambdaTest{

public static void main(String[] args) {
RunningWomen runningWomen = new RunningWomen();
Person superPerson = runningWomen.createSuperPerson();
Person thisPerson = runningWomen.createThisPerson();
superPerson.run("huihui");
thisPerson.run("huihui");
}
}

interface Person {

public void run(String name);
}

class Woman {

public void run(String name) {
System.out.println(name + "在为减肥跑步。");
}
}

class RunningWomen extends Woman {

@Override
public void run(String name) {
System.out.println(name + "在晨练跑步。");
}

public Person createSuperPerson() {
return super::run;
}

public Person createThisPerson() {
return this::run;
}
}

运行结果:

1
2
huihui在为减肥跑步。
huihui在晨练跑步。

构造器引用

可以应用一个对象的构造器,将一个变量转化为其他类型的变量。

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
package com.example.demo.test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class LambdaTest{

public static void main(String[] args) {
List<String> integerList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
integerList.add(String.valueOf(i));
}

List<Woman> womanList = integerList.stream().map(Woman::new).collect(Collectors.toList());
womanList.forEach(System.out::println);
}
}

class Woman {

private String name;

public Woman(String name) {
this.name = name;
}

@Override
public String toString() {
return "Woman{" +
"name='" + name + '\'' +
'}';
}
}

运行结果:

1
2
3
4
5
Woman{name='0'}
Woman{name='1'}
Woman{name='2'}
Woman{name='3'}
Woman{name='4'}

变量作用域

在lambda表达式中,可以使用表达式之外的变量。

1
2
3
4
5
6
7
8
9
10
11
12
public class LambdaTest {

public static void main(String[] args) {
String name = "huihui";
Person person = () -> System.out.println(name + " is running.");
person.run();
}
}

interface Person {
void run();
}

但是,在lambda表达式中只能使用值不会改变的变量。不可以在lambda表达式中改变变量的值,因为lambda表达式是线程不安全的。如以下写法:

1
Person person = () -> name = "xixi";

若变量在lambda表达式外部发生变化,也是不允许的。 如一下写法:

1
2
3
4
for (int i = 0; i < 10; i++) {
Person person = () -> System.out.println(name + " is running." + i);
person.run();
}

以上均会提示Variable used in lambda expression should be final or effectively final

在方法中不能声明同名的变量,因此 lambda表达式中声明与一个局部变量同名的参数或局部变量也是不合法的,如以下写法:

1
2
String name = "huihui";
Person person = name -> System.out.println(name + " is running.");

lambda表达式中的this关键字指的是外部的含义,并没有什么改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LambdaTest{

public static void main(String[] args) {
LambdaTest lambdaTest = new LambdaTest();
Person person = lambdaTest.createPerson();
person.run();
}

public Person createPerson() {
return () -> System.out.println(this.getClass().getName());
}
}

interface Person {
void run();
}

运行结果:

1
com.example.demo.test.LambdaTest

this其实就是createPerson方法的this。

处理lambda表达式

使用lambda表达式的重点是延迟执行,如:

  • 在一个单独的线程中运行代码;
  • 多次运行代码;
  • 在算法的适当位置运行代码(如排序中的比较算法);
  • 发生某种情况时执行代码(如点击按钮等);
  • 只在必要时才运行代码。

以下是常用的函数式接口,在开发过程中,可以使用这些函数式接口。

函数式接口 参数类型 返回类型 抽象方法名 描述 其他方法
Runnable void run 作为无参数或返回值的动作运行
Supplier T get 提供一个T类型的值
Consumer T void accept 处理一个T类型的值 andThen
BigConsumer<T, U> T, U void accept 处理T和U类型的值 andThen
Function<T, R> T R apply 有一个T类型参数的函数 compose, andThen, identity
BigFunction<T, U, R> T, U R apply 有T和U类型参数的函数 andThen
UnaryOperator T T apply 类型T上的一元操作符 compose, andThen, identity
BinaryOperator T, T T apply 类型T上的二元操作符 andThen, maxBy, minBy
Predicate T boolean test 布尔值函数 and, or, negate, isEqual
BigPredicate<T, U> T, U boolean test 有两个参数的布尔值函数 and, or, negate

以下是基本类型的函数式接口。

函数式接口 参数类型 返回类型 抽象方法名
BooleanSupplier boolean getAsBoolean
PSupplier p getAsP
pConsumer p void accept
ObjpConsumer T, p void accept
pFunction p T apply
pToQFunction p q applyAsQ
TopFunction T p applyAsp
TopBiFunction<T, U> T, U p applyAsp
pUnaryOperator p p applyAsp
pBinaryOperator p, p p applyAsp
pPredicate p boolean test

注:p, q为int , long, double; P, Q为Integer, Long, Double

在使用函数式接口时,最好使用上述两个表的接口,要注意根据特定情况使用特定的接口,减少自动装箱。如以下代码,使用IntConsumer而不是Consumer,因为传入的是int类型的参数。

1
2
3
4
IntConsumer intConsumer = System.out::println;
for (int i = 0; i < 10; i++) {
intConsumer.accept(i);
}

@FunctionalInterface注解表示接口是一个函数式接口,自己定义的函数式接口最好使用此注解进行标记,避免无意增加一个非抽象方法。

-------------本文结束感谢您的阅读-------------

本文标题:Java lambda表达式

文章作者:huihui

发布时间:2018年10月09日 - 09:10

最后更新:2019年02月14日 - 19:02

原始链接:http://101.200.47.120:8011/2018/10/09/Java-lambda表达式/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。