• 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

  • 相关阅读:
    麻省理工算法导论学习笔记(1)算法介绍
    麻省理工算法导论学习笔记(2)渐近符号、递归及解法
    Mybatis if 标签 判断不生效
    Linux permission denied解决方法?
    MySQL查找是否存在
    List集合数据去重
    Java获取list集合的前几个元素
    git如何新建(修改)分支
    asp.net下url参数含有中文读取后为乱码
    时间复杂度为O(n)的排序算法
  • 原文地址:https://www.cnblogs.com/iandf/p/13973554.html
Copyright © 2020-2023  润新知