如果Lambda表达式和方法引用
1.名词解释
行为参数化:让方法接收多种行为作为参数,从而表现出不同的功能。
实现途径:
可以实现匿名内部类,也可以使用策略模式(算法族和策略),但是这两种都是传递的对象,你想写的行为逻辑要被包含在对象里面传递过去,很繁琐。
使用Lambda表达式和方式引用传递行为是更简便,代码的含义更加清晰。
Lambda表达式是函数是接口的一个具体实现的实例,函数式接口的抽象方法签名可以描述Lambda表达式的签名,函数是接口的抽象方法签名就是函数描述符
表达式与语句的区别
expression(表达式)是指由bai变量、操作符、字面量du和方法调用组成的一个zhi结构,一个表达式有dao一个计算结果
例如 a = 3 是一个表达式,他的计算结果为3,3>2 是一个 表达式,计算结果为 true
statement(语句)在java中是一个完整的执行单元,类似于日常生活中的一句话。我们说一句话需要有一个句号,同样的在java中一个语句须由“;”结尾。
如 int a=3;是一个语句 retun 3;也是一个语句。
两者的区别:语句可以由表达式组成,当然语句内也可以没有表达式,两者是描述代码不同粒度的单位。
自由变量:不是方法的参数,而是外层作用域定义的变量
2.函数式接口
java8引入了三个新的函数式接口,以便于我们更方便的使用Lambda表达式
2.1 Predicate
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
如果需要一个涉及到T的boolean类型的表达式时可以使用Predicate
2.2 Consumer
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
如果你需要访问类型为T的对象并对其执行一些操作的话,可以使用Consumer
2.3 Function
public interface Function<T, R> {
/**
接受一个T对象,返回一个R对象
*/
R apply(T t);
}
如果你需要将输入对象的信息映射到输出,可以使用Function
定义一个map方法使用Funcation接口将苹果列表映射成苹果重量的列表
public static <T,R> List<R> map(List<T> sourceList, Function<T,R> function){
ArrayList<R> resultList = new ArrayList<>();
for (T t : sourceList) {
R r = function.apply(t);
resultList.add(r);
}
return resultList;
}
使用:
List<Apple> inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "green"), new Apple(120, "red"));
List<Integer> weightList = map(inventory, (Apple a) -> a.weight);
还有其他的函数式接口,这里讲一些常用的
3.Lambda表达式的类型检查,类型推断和限制
3.1 类型检查
Lambda的类型是从lambda表达式的上下文推断出来的。目标类型是指Lambda表达式所需要的类型。上下文(可能是方法的参数,也可能是接收它的值的局部变量。)
利用目标类型来检査一个 Lambda,是否可以用于某个特定的上下文
3.2 类型推断
Lambda表达式可以根据上下文来推断出适合Lambda的签名,因此可以省略参数类型
List<Integer> weightList = map(inventory, (a) -> a.weight);
3.3 使用局部变量
Lambda可以无条件使用实例变量和静态变量,但是使用局部变量必须有final修饰,不能修改定义 Lambda的方法的局部变量的内容。这些变量必须是隐式最终的。可以认为 Lambda是对值封闭,而不是对变量封闭。
局部变量必须有final修饰的原因:
这种限制存在的原因在于局部变量保存在栈上,并且隐式表示它们仅限于其所在线程。如果允许捕获可改变的局部变量,就会引发造成线程不安全的新的可能性,而这是我们不想看到的(实例变量可以,因为它们保存在堆中,而堆是在线程之间共亭的)。
4. 方法引用
方法引用就是可以使用现有的方法定义,像lambda一样传递它们
List<Apple> greenApples = filterApple(inventory, FilteringApples::isGreen);
isGreen是在FilteringApples类中定义好的静态方法。
方法引用的定义: ::之前是目标引用,之后是方法名称。上面的方法引用和下面的lambda表达式等价
List<Apple> greenApples = filterApple(inventory, (a) -> "green".equals(a.getColor()));
方法引用的构建:
- 指向静态方法的方法引用:FilteringApples::isGreen
- 指向任意类型实例方法对的方法引用:String::length
- 指向现有对象的实例方法的方法引用 filteringApples::isHeavy, isHeavy不是静态方法,::之前是已经存在的对象
第二条和第三条有点混淆,第二条具体是指,引用一个对象的方法,而这个对象正好是lambda的参数,就可以这样写。比如
Function<String,Integer> predicate = (s) -> s.length();
//上面就是引用一个对象的方法,而这个对象正好是lambda的参数,所以可以简写
Function<String,Integer> predicate1 = String::length;
图解方法引用的构建
练习:根据重量对苹果进行排序
//第一步,使用lambda表达式
inventory.sort((a1,a2)->a1.getWeight().compareTo(a2.getWeight()));
//第二步,comparing方法接收一个Function函数,返回一个Comparator实例
inventory.sort(Comparator.comparing((a)->a.getWeight()));
//第三步,使用方法引用
inventory.sort(Comparator.comparing(Apple::getWeight));
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor){
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
5. 复合Lambda表达式
java8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递 Lambda表达式的 Comparator、 Function和 Predicate都提供了允许你进行复合的方法。这是什么意思呢?在实践中,这意味着你可以把多个简单的 Lambda复合成复杂的表达式。比如你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。
5.1 比较器复合
5.1.1 逆序
可以使用已有的比较器,通过reversed方法使给定的比较器逆序
List<Apple> inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "red"), new Apple(155, "green"));
//按升序排列
Comparator<Apple> comparingWeightASC = Comparator.comparing(Apple::getWeight);
inventory.sort(comparingWeightASC);
printInventory(inventory);
//按降序排列
System.out.println("==========================reversed=================================");
Comparator<Apple> comparingWeightDESC = comparingWeightASC.reversed();
inventory.sort(comparingWeightDESC);
printInventory(inventory);
5.1.2 比较器链
比如实现需求:先按降序排列,如果重量相同,再按颜色排列。
可以使用thenComparing方法,含义是:如果第一个比较器比较的结果一致,那么就使用第二个比较器
Comparator<Apple> comparingColorASC = Comparator.comparing(Apple::getColor);
//如果第一个比较器比较的结果一致,那么就使用第二个比较器
Comparator<Apple> weightDescAndColor = comparingWeightDESC.thenComparing(comparingColorASC);
inventory.sort(weightDescAndColor);
printInventory(inventory);
测试结果
5.2 谓词复合
谓词复合使用这三种方法 negate、 or、 and。
negate返回一个Predicate的非
筛选出不是绿色的苹果
List<Apple> inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "red"), new Apple(155, "green"));
Predicate<Apple> greenApple = (a)->"green".equals(a.getColor());
List<Apple> greenApples = filter(inventory, greenApple);
printInventory(greenApples);
System.out.println("==========================negate=================================");
inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "red"), new Apple(155, "green"));
//不是绿苹果
Predicate<Apple> notGreenApple = greenApple.negate();
List<Apple> notGreenApples = filter(inventory, notGreenApple);
printInventory(notGreenApples);
筛选出不是绿苹果或者重量大于150的苹果
System.out.println("==========================or=================================");
inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "red"), new Apple(155, "green"));
//不是绿苹果或者重量大于150
Predicate<Apple> heavyApple = (a)->150 < a.getWeight();
Predicate<Apple> notGreenOrHeavyApple = greenApple.negate().or(heavyApple);
List<Apple> notGreenOrHeavyApples = filter(inventory, notGreenOrHeavyApple);
printInventory(notGreenOrHeavyApples);
筛选出红苹果且重量大于150的苹果
System.out.println("==========================and=================================");
//是红苹果且重量大于150
inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "red"), new Apple(155, "green"));
Predicate<Apple> redAndHeavyApple = heavyApple.and((a) -> "red".equals(a.getColor()));
List<Apple> redAndHeavyApples = filter(inventory, redAndHeavyApple);
printInventory(redAndHeavyApples);
测试结果
NOTE:
谓词是按照从左到右的优先级的。比如 a.or(b).and(c) 等价于 (a||b) && c
5.3 函数复合
可以把 Function接口所代表的 Lambda表达式复合起来。 Function接口为此配了 anchen和 compose两个默认方法,它们都会返回 Function的一个实例
anThen和compose方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数,调用的顺序相反而已
假设有一个函数f(x) 给数字加1(x->x+1),另一个函数g(x)给数字乘2,你可以将它们组合成一个函数h(x),先给数字加1,再给结果乘2。h(x) = g(f(x))
Function<Integer,Integer> f = (x) -> x + 1;
Function<Integer,Integer> g = (x) -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);
System.out.println("h(1) -> "+result);//结果为4
假设有一个函数f(x) 给数字加1(x->x+1),另一个函数g(x)给数字乘2,你可以将它们组合成一个函数h(x),先给数字乘2,再给结果加1。h(x) = f(g(x))
Function<Integer, Integer> h2 = f.compose(g);
result = h2.apply(1);
System.out.println("h2(1) -> "+result);//结果为3
测试结果: