title: Lambda学习
date: 2020-12-14 11:48:45
categories: java8
tags:
- java8新特性
行为参数化
让方法接受多种行为(策略)作为参数,并在内部使用来完成不同的行为,java8之前用的方法是,传递一个实现某个接口的匿名类比如:
public interface ApplePredicate{
boolean test(Apple a);
}
List<Apple> redApples=filter(apples,new ApplePredicate(){
public boolean test(Apple a){
return RED.eauals(a.getColor);
}
})
这种采用匿名类的方式非常繁琐,于是java8给我们提供了新的强大工具---Lambda表达式。
Lambda表达式
也称为闭包,运行把函数作为一个方法的参数,主要用来定义方法类型接口
举例说明:
- 不需要参数,返回值为5
()->5 - 接收一个参数,返回函数运算值
x ->x*2 - 接收多个参数
(x,y)->x-y - 接收带类型的参数
(int x,int y)->x+y - 接收参数,但不返回(返回void)
(String s)->sout(s)
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明,使用类型推断
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
//如果接口内只有一个方法则可以
MathOperation addtion=(int a,int b)->a*b;
System.out.println(addtion.operation(1,2));
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
常见函数式接口:
函数接口 | 使用案例 | 例子 |
---|---|---|
Predicate<T> | 布尔表达式 | (Integer a)-> a>0 |
Consumer<T> | 布尔表达式 | (Integer a)-> |
Comparator<T> | 布尔表达式 | (Integer a,Integer b)-> a-b |
以上都来自于java.util.function
包中,且有对应的原型类接口,比如IntPredicate.
- Lambda中在使用局部变量时必须是final或者事实final的,但实例变量不要求,为什么?
因为Lambda实际上只是一个匿名类的一个实例,那么在匿名类初始化时自然会直接将此变量赋值过来(注意是在无参构造器中传入)的一个副本,那么当这个实例被真正调用时,在创建它的线程中可能这个变量已经更改了,于是出现这两个值不一样,导致结果不符合预期。而实例变量不一样,传入的是一个引用,所以实例变量被修改时,Lambda表达式实例能够感知到。所以要求局部变量需要final,而实例变量不需要。
方法引用
之前看过一个语法:
Supplire<Apple>c1=Apple::new;
可能开始会非常疑惑,这个明显是C++的语法,怎么java也能用了呢?
其实不是,我们可以把它当成是java8对lambda表达式的一个语法糖,它可以被看做调用特定方法的Lambda的一种快捷写法。没有括号是因为它只是我们引用的一个名字,并没有调用它。
方法引用主要有三种:
- 指向静态方法的引用,比如Integer::parseInt.
- 指向任意类型实例方法的方法引用,比如String::length.
- 指向现存对象或表达式实例方法的引用,比如apple::getColor.
第一种和第二种比较好理解,主要记住第三种,一些辅助类方法比较常用。
复合Lambda表达式的用法
java8中好几个函数式接口都有提供允许我们进行复合计算的方法,可以让我们方便的实现一些语法上的复合操作,比如与、或,这些都是默认方法。
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
可以从源码上可以看到,其原理就是内部实现调用上一步的结果,再进行一步计算,最后返回同样的类型,这个有点类似构建器模式,也有点想数学中的函数传递,比如f(g(x))这样。