# 语法
(参数) -> {表达式} ,形如:
(String first, String second) -> first.length() - second.length() |
# 函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个 lambda 表达式。这种接口称为函数式接口。
lambda 表达式可以看做一个函数,并且 lambda 表达式可以传递到函数式接口。如下的 Person 接口:
interface Person { | |
public void run(String name); | |
} |
可以使用 lambda 表达式实现接口的方法:
Person person = name -> System.out.println(name + " is running."); |
# 方法引用
上述 lambda 表达式也可以传递一个方法的引用:
Person person = System.out::println; |
有三种方法引用的情况:
- object::instanceMethod
传递一个对象的方法。
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 + "在为减肥跑步。"); | |
} | |
} |
运行结果:
huihui在为减肥跑步。
- Class::staticMethod
传递一个类的静态方法。
Person person = System.out::println; |
- Class::instanceMethod
传递一个类的实例方法。第一个参数会成为方法的目标,例如, String::compareToIgnoreCase
等同于 (x, y) -> x.compareToIgnoreCase(y)
。
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 + "在为减肥跑步。"); | |
} | |
} |
运行结果:
huihui在为减肥跑步。
方法引用中,使用 this 或者 super 也是合法的。
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; | |
} | |
} |
运行结果:
huihui在为减肥跑步。
huihui在晨练跑步。
# 构造器引用
可以应用一个对象的构造器,将一个变量转化为其他类型的变量。
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 + '\'' + | |
'}'; | |
} | |
} |
运行结果:
Woman{name='0'}
Woman{name='1'}
Woman{name='2'}
Woman{name='3'}
Woman{name='4'}
# 变量作用域
在 lambda 表达式中,可以使用表达式之外的变量。
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 表达式是线程不安全的。如以下写法:
Person person = () -> name = "xixi"; |
若变量在 lambda 表达式外部发生变化,也是不允许的。 如一下写法:
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 表达式中声明与一个局部变量同名的参数或局部变量也是不合法的,如以下写法:
String name = "huihui"; | |
Person person = name -> System.out.println(name + " is running."); |
lambda 表达式中的 this 关键字指的是外部的含义,并没有什么改变。
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(); | |
} |
运行结果:
com.example.demo.test.LambdaTest
this 其实就是 createPerson
方法的 this。
# 处理 lambda 表达式
使用 lambda 表达式的重点是延迟执行,如:
- 在一个单独的线程中运行代码;
- 多次运行代码;
- 在算法的适当位置运行代码(如排序中的比较算法);
- 发生某种情况时执行代码(如点击按钮等);
- 只在必要时才运行代码。
以下是常用的函数式接口,在开发过程中,可以使用这些函数式接口。
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 无 | void | run | 作为无参数或返回值的动作运行 | |
Supplier<T> | 无 | T | get | 提供一个 T 类型的值 | |
Consumer<T> | 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 | T | apply | 类型 T 上的一元操作符 | compose, andThen, identity |
BinaryOperator<T> | T, T | T | apply | 类型 T 上的二元操作符 | andThen, maxBy, minBy |
Predicate<T> | 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> | T, p | void | accept |
pFunction<T> | p | T | apply |
pToQFunction | p | q | applyAsQ |
TopFunction<T> | 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
类型的参数。
IntConsumer intConsumer = System.out::println; | |
for (int i = 0; i < 10; i++) { | |
intConsumer.accept(i); | |
} |
@FunctionalInterface
注解表示接口是一个函数式接口,自己定义的函数式接口最好使用此注解进行标记,避免无意增加一个非抽象方法。