Lambda表达式

如果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);

还有其他的函数式接口,这里讲一些常用的

image-20201113204035680

3.Lambda表达式的类型检查,类型推断和限制

3.1 类型检查

Lambda的类型是从lambda表达式的上下文推断出来的。目标类型是指Lambda表达式所需要的类型。上下文(可能是方法的参数,也可能是接收它的值的局部变量。)

利用目标类型来检査一个 Lambda,是否可以用于某个特定的上下文

image-2020111320414612

3.2 类型推断

Lambda表达式可以根据上下文来推断出适合Lambda的签名,因此可以省略参数类型

List<Integer> weightList = map(inventory, (a) -> a.weight);

3.3 使用局部变量

image-20201114092613718

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()));

方法引用的构建:

  1. 指向静态方法的方法引用:FilteringApples::isGreen
  2. 指向任意类型实例方法对的方法引用:String::length
  3. 指向现有对象的实例方法的方法引用 filteringApples::isHeavy, isHeavy不是静态方法,::之前是已经存在的对象

第二条和第三条有点混淆,第二条具体是指,引用一个对象的方法,而这个对象正好是lambda的参数,就可以这样写。比如

Function<String,Integer> predicate = (s) -> s.length();
//上面就是引用一个对象的方法,而这个对象正好是lambda的参数,所以可以简写
Function<String,Integer> predicate1 = String::length;

图解方法引用的构建

image-2020111409504424

练习:根据重量对苹果进行排序

//第一步,使用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);

测试结果

image-2020111410282933

5.2 谓词复合

谓词复合使用这三种方法 negateorand

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);

测试结果

image-20201114105249879

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

测试结果:

image-20201114110841007

原文地址:https://www.cnblogs.com/iandf/p/13973554.html