由来:
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的「λ演算」得名,直接对应于其中的lambda抽象(lambda abstraction)。
λ这个符号读lambda,所以匿名函数又被称为lambda函数,最早lambda函数应该是出现Lisp中的,因为它是一门「纯函数式编程的语言」。
λ演算(英语:lambda calculus,λ-calculus)是一套从数学逻辑中发展,以变量绑定和替换的规则,来研究函数如何抽象化定义、函数如何被应用以及递归的形式系统。
它是java从函数式编程语言借鉴,方便开发人员编写清晰、高效代码的一种编程风格。
java8中从函数式编程引入了两个核心思想:方法(方法引用)和lambda表达式可以作为值传递,并且函数或者方法可以有效、安全地并行执行。
Lambda 在编程语言中往往是一个匿名函数,也就是说Lambda 是一个抽象概念,而编程语言提供了配套支持,比如在 Java 中其实为Lambda 进行配套的就是函数式接口,通过函数式接口生成匿名类和方法进行Lambda 式的处理。
栗子:
// 苹果
public class Apple {
private String color;
// Apple类中行为参数化
public interface ApplePredicate {
boolean filter(Apple apple);
}
// Apple类中用来筛选属性的方法
public static List<Apple> filter(List<Apple> apples, ApplePredicate predicate) {
List<Apple> filterList = new ArrayList<>();
for (Apple apple : apples) {
if (predicate.filter(apple)) {
filterList.add(apple);
}
}
return filterList;
}
}
List<Apple> apples = new ArrayList<Apple>();
...
// => 传统遍历
// => 筛选出列表中颜色为绿色的苹果
List<Apple> greenApples = new ArrayList<>();
for (Apple apple : apples) {
if ("green".equals(apple.getColor())) {
greenApples.add(apple);
}
}
// => 3种实现方式
// 1. ApplePredicate实现类
public class GreenApplePredicate implements ApplePredicate {
@Override
public boolean filter(Apple apple) {
return "green".equals(apple.getColor());
}
}
List<Apple> f1 = Apple.filter(apples, new GreenApplePredicate());
// 2. 匿名内部类
List<Apple> f2 = Apple.filter(apples, new ApplePredicate() {
@Override
public boolean filter(Apple apple) {
return "green".equals(apple.getColor());
}
});
// 3. lambda表达式
List<Apple> f3 = Apple.filter(apples
, (Apple apple) -> "green".equals(apple.getColor()));
lambda函数优缺点
- 优点:
代码简洁,开发迅速
函数式编程 - 缺点:
不建议在lambda函数内写太复杂的条件语句,不易读;
lambda函数与普通函数相比,不会提高程序运行效率的提高,很多计算未必有传统的 for 性能要高
不容易进行调试
Java中使用
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
栗子:
// 无参无返回值
TestA testA = () -> System.out.print("无参无返回值\n");
testA.exec();
// 无参有返回值
TestB testB = () -> "无参有返回值\n";
System.out.print(testB.exec());
// 有参无返回值
TestBB testBB = (String s) -> System.out.print(s);
testBB.exec("有参无返回值\n");
// 有参有返回值
TestC testC = (String a, String b) -> a + b;
System.out.print(testC.exec("2个参数", "有返回值\n"));
// 不可移至尾部
textD(1, 2, (int x, int y) -> x + y);
// 局部变量截获
NumAdd a = add();
System.out.print(a.exec(10) + "\n");
System.out.print(a.exec(20) + "\n");
int total = 0;
private NumAdd add() {
//int total = 0;
//可以捕获局部变量,但不能对局部变量修改,默认是final类型
return (int num) -> {
total += num;
return total;
};}
private void textD(int p1, int p2, Fn fn) {
System.out.print(fn.exec(p1, p2) + "\n");}
public interface TestA { void exec(); }
public interface TestB { String exec(); }
public interface TestBB { void exec(String s); }
public interface TestC { String exec(String a, String b); }
public interface Fn { int exec(int a, int b); }
public interface NumAdd { int exec(int num); }
kotlin中使用
//表示无参数无返回值的Lambda表达式类型
{() -> Unit}
//表示接收一个T类型参数,无返回值的Lambda表达式类型
{(T) -> Unit}
//表示接收一个T类型参数,返回一个R类型值的Lambda表达式类型
{(T) -> R}
//表示接收一个T类型和P类型的参数,返回一个R类型值的Lambda表达式类型
{(T, P) -> R}
//表示接收一个T类型参数和一个接收P、Q类型两个参数并返回一个S类型的值的Lambda表达式类型参数
//,返回一个R类型值的Lambda表达式类型
{(T, (P,Q) -> S) -> R}
栗子:
// 无参无返回值
val testA = { print("无参无返回值\n") }
testA()
// 无参有返回值
val testB = { "无参有返回值\n" }
print(testB())
// 有参无返回值
val testBB: (String) -> Unit = { print(it) }
testBB("有参无返回值\n")
// 有参有返回值
val testC = { strA: String, strB: String
-> strA + strB }
print(testC("2个参数", "有返回值\n"))
// 最后一个参数是lambda表达式, 可移至尾部
textD(1, 2) { x: Int, y: Int -> x + y}
// 局部变量截获
val a = add()
print(a(10).toString() + "\n")
print(a(20).toString() + "\n")
fun add() : (Int) -> Int {
var total = 0
return { num: Int -> total += num
total
}}
fun textD(p1: Int, p2:Int ,fn: (Int, Int) -> Int) {
print(fn(p1, p2).toString() + "\n")}
规则:
java:
- 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
- 参数的小括号里面只有一个参数,那么小括号可以省略
- 如果方法体当中只有一句代码,那么大括号可以省略
- 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字。
kotlin:
lambda表达式是总是由花括号{}包裹
lambda表达式声明的函数参数列表在 -> 前面,与普通函数参数声明方式一样,但:
* 参数列表不能用括号()包裹
* 没有任何参数时,则不需要括号和->
* 只有一个参数时,则可以省略参数使用默认的it代替,同时省略->lambda表达式声明的函数体在 -> 后面
lambda表达式声明的函数的返回值不是Unit,则最后一个表达式作为函数的返回值
不同语言中的lambda
// =>Python
lambda [arg1[,arg2,arg3....argN]]:expression
// 栗子:
add2 = lambda x,y:x+y
print add2(1,2) #3
sum2 = lambda x,y=10:x+y
print sum2(1) #11
print sum2(1,100) #101
// =>C++
[ capture clause ] (parameters) -> return-type { definition of method }
// 栗子:
void func(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int i) {
cout << i << endl;
});
}
// => Javascript
(p1 [,p2,p3,....pn]) => { code block }
// 栗子:
let func = x => x * x;
func(2) #4
// => OC - Block 类似lambda
^返回值 参数列表 表达式
// 栗子:
void (^blockTest)(void) = ^{
NSLog(@"block");
};
blockTest();
// 带参
^(int a){
NSLog(@"block的使用");
};
// 不带参
^{
NSLog(@"最简捷的block的使用");
};
// block作为参数
void funcWithParm(void (^block)(int parm))
{
block(10);
}
// swift 闭包
// 无参无返回值
let testA = { print("无参无返回值闭包") }
testA()
// 无参有返回值
let testB = { return "无参有返回值闭包" }
print(testB())
// 有参有返回值
let testC = {(strA: String, strB: String)
in return strA + strB }
print(testC("2个参数", "有返回值闭包"))
// 尾随闭包
textD(p1: 1, p2: 2) {
return $0 + $1 // #3
}
func textD(p1: Int, p2:Int ,fn: (Int, Int) -> Int) {
print(fn(p1, p2))
}
// 闭包可能捕获非常量变量
let a = add();
print(a(10)) // # 10
print(a(20)) // # 30
func add() -> (Int) -> Int {
var total = 0
return {num in
total += num;
return total;
}
}
函数式接口
- @FunctionalInterface注解
有且只有一个抽象方法的接口被称为函数式接口,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法(equal和hashcode方法不算),否则将会报错。
但是这个注解不是必须的,只要符合函数式接口的定义,那么这个接口就是函数式接口。
// forEach 参数类型
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
- Consumer: 消费性接口
Consumer通过名字可以看出它是一个消费函数式接口,主要针对的是消费(1..n 入参, 无返回)这个场景,典型的例子是 forEach 方法
public interface Iterable<T> {
...
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
// 栗子:
List<Integer> list = Arrays.asList(1,2,3,4);
list.forEach(item -> {
System.out.print(item);
});
// =>
list.forEach(System.out::println);
- Supplier: 供给型接口
Supplier通过名字比较难看出来它是一个场景的函数式接口,它主要针对的是说获取(无入参,有返回)这个场景,典型的例子是 Stream 中的 collect 方法,通过自定义传入我们想要取得的某种对象进行对象收集
// collect 参数类型
@FunctionalInterface
public interface Supplier<T> {
T get();
}
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
public interface Stream<T> extends BaseStream<T, Stream<T>> {
...
<R, A> R collect(Collector<? super T, A, R> collector);
}
// 栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> newNumbers = numbers.stream()
.filter(x -> x >= 2)
.collect(Collectors.toList()); // 将大于等于2的数重新收集成一个集合
- Function: 函数型接口
Function 接口的名字不太能轻易看出来它的场景,它主要针对的则是 转换(有入参,有返回,其中T是入参,R是返回)这个场景,典型的例子是Stream 中 map 系列方法和 reduce 系列方法
// map 参数类型
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
...
}
public interface Stream<T> extends BaseStream<T, Stream<T>> {
...
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
T reduce(T identity, BinaryOperator<T> accumulator);
}
// 栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> doubleNumbers = numbers.stream()
.map(number -> number * 2)
.collect(Collectors.toList());
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
Integer sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
// reduce 参数类型
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
...
}
- Predicate: 断言型接口
Predicate主要针对的是判断(有入参,有返回,凡是返回的类型固定为Boolean。可以说Function 是包含Predicate的 )这个场景,典型的例子是Stream 中 filter方法
// filter参数类型
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
...
}
public interface Stream<T> extends BaseStream<T, Stream<T>> {
...
Stream<T> filter(Predicate<? super T> predicate);
}
Stream表达式
Stream,就是JDK8又依托于函数式编程特性为集合类库做的一个类库,它其实就是jdk提供的函数式接口的最佳实践。它能让我们通过lambda表达式更简明扼要的以流水线的方式去处理集合内的数据,可以很轻松的完成诸如:过滤、分组、收集、归约这类操作。
其中Stream的操作大致分为两类
-
中间型操作
-
终结型操作
Lambda原理