lambda 表达式之方法传递 ①

120 阅读5分钟

作为lambda表达式的开门之作,最终目标是为了说明lambda表达式的妙用。但不使用lambda表达式,我们的程序毅然可以运行顺畅,可能运行效率更高。但为什么我们毅然要学习,其实终极目标是为了减少代码编写量,编写出人更能看懂的代码

我们回忆下,假设我们要对一个集合数据进行过滤或分组操作,在我们还是刚入门的时候,我们会怎么做?

定义一个实体类Apple,我们接下来的操作都主要针对它进行:

package com.noah;

import lombok.Data;
import org.springframework.util.StringUtils;

@Data
public class Apple {
    private double weight;
    private String country;

    private String color = "red";

    /**
     * 创建Apple实例的静态方法
     * @param weight
     * @param country
     * @return
     */
    public static Apple createNewInstance(int weight, String country) {
        return new Apple(weight, country);
    }

    public Apple(int weight, String country) {
        this(weight, country, null);
    }

    public Apple(int weight, String country, String color) {
        this.weight = weight;
        this.country = country;
        this.color = StringUtils.isEmpty(color) ? "red" : color;
    }

}

接下来构造一个单元测试类

package com.noah;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;

class SpringApplicationStartupTest {

    @Test
    void map() {
        List<Apple> appleList = this.generateAppleList();

        System.out.println(Arrays.toString(appleList.toArray()));
        
    }

    /**
     * 构造Apple 集合列表数据
     *
     * @return
     */
    private List<Apple> generateAppleList() {
        List<Apple> appleList = new ArrayList<>();
        // 构造新的苹果实例 使用的是lambda表达式的方法引用特例:构造函数引用
        BiFunction<Integer, String, Apple> createAppleInstance = Apple::new;
        // 使用lambda表达式进行构造
        appleList.add(createAppleInstance.apply(10, "中国"));
        appleList.add(createAppleInstance.apply(20, "中国台湾"));
        appleList.add(createAppleInstance.apply(5, "泰国"));

        // 使用Apple 静态函数进行构造
        appleList.add(Apple.createNewInstance(20, "宇宙曹县"));

        // 使用new 进行构造
        appleList.add(new Apple(50, "世界单县"));

        return appleList;
    }
}

如果你看到了测试代码中的构造函数引用,并且深入理解它,那么你已经了解并掌握了lambda表达式。 如果你还不知道为啥可以这么写?那么我们继续我们的探索之旅。

客户来了需求,需求列表如下:

  1. 按照weight 进行降序排列
  2. 按照weight进行分组
  3. 按照weight进行升序排列
  4. 其他各种需求

针对当前已知需求,如果我们要实现,假定我们不知道lambda为何物的前提下,我们会怎么做呢? 我们新建一个Class,用以将集合升序排列:

package com.noah;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Apple 升序排列
 */
public class AppleWeightAscFilter {
    public static void asc(List<Apple> sourceList) {
        for (int i = 0; i < sourceList.size(); i++) {
            for (int j = i;
                 j > 0 && sourceList.get(j - 1).getWeight() > sourceList.get(j).getWeight();
                 j--) {
                Apple tempApple = sourceList.get(j);
                sourceList.set(j, sourceList.get(j - 1));
                sourceList.set(j - 1, tempApple);
            }
        }
    }
}

针对降序排列也是同样的处理方式,增加一个Class进行处理,这完全符合Java面向对象的处理方式,一切均对象。 这也是最基础、最底层的集合处理方式,后面我们会看到sort方法,调用起来那么非常的酸爽, 一个参数搞定,但后端底层的实现也是我们的这种写法, 因此我们常说一句话,此处不知道当讲不当讲。 在我们面试的时候,会经常看到一个要求,具有一定的算法基础, 但Java这些语言却拿我们当傻子,将底层算法进行了大量的封装, 导致我们的算法基础得不到思考。但是,程序员是自强不息的一批人, 虽然他们进行了封装,但是我们仍然要看到这背后进行的大量基础工作。 也就是我们常说的Java基础,究竟怎么样才算会Java基础呢? 具有看破一切的能力才应该算,而不是仅仅知道几个java语法。

测试下我们的这个class处理:


@Test
void map() {
    List<Apple> appleList = this.generateAppleList();

    AppleWeightAscFilter.asc(appleList);

    System.out.println(Arrays.toString(appleList.toArray()));
    
    // 输出结果 [Apple(weight=5.0, country=泰国, color=red), Apple(weight=10.0, country=中国, color=red), Apple(weight=20.0, country=中国台湾, color=red), Apple(weight=20.0, country=宇宙曹县, color=red), Apple(weight=50.0, country=世界单县, color=red)]


}

我们今天的题目是方法传递,到现在没看到任何方法传递的说明呢,马上开饭

梳理下我们的需求,我们可以发现所有的处理都是来源于一个Apple的集合,看到此处,我脑海之中有了一个大胆的想法,是不是可以通过面向接口编程的形式,通过接口来进行定义,然后运行策略模式来构造不同的实例呢?

这是我们代码迈向正规的第一步,面向接口编程:

定义接口:


package com.noah;

import java.util.List;

/**
 * 定义接口 Apple 处理
 */
public interface AppleFilter {
    /**
     * Apple 集合处理方法
     *
     * @param sourceList  Apple数据源参数
     */
    void execute(List<Apple> sourceList);
}

调整当前的实现Class,继承自AppleFilter,然后调整调用形式:

 
@Test
void map() {
    List<Apple> appleList = this.generateAppleList();

    AppleFilter ascAppleFilter = new AppleWeightAscFilter();
    ascAppleFilter.execute(appleList);


    System.out.println(Arrays.toString(appleList.toArray()));
    // 输出了同样正确的结果

}

但是现在我们面临了另一个问题,具有那么多实现类,我们每次都实例化,但是处理方式是同样的,因此我们又有了一个大胆的想法,将execute的调用独立出来,这样我们无需每次都定义接口实例,直接传递对应的实现类实例进行处理,


@Test
void map() {
    List<Apple> appleList = this.generateAppleList();
    executeFilter(new AppleWeightAscFilter(), appleList);
    //executeFilter(new AppleWeightDescFilter(), appleList);
    //executeFilter(new AppleWeightGroupFilter(), appleList);
}

private void executeFilter(AppleFilter appleFilter, List<Apple> appleList) {
    appleFilter.execute(appleList);
    System.out.println(Arrays.toString(appleList.toArray()));
}

看到此处,我们发现处理端是统一的方法处理,我们传递的是实现类实例,方法传递包含在了具有固定名称的Class类里面。

这是我们普通的方法传递形式,通过接口定义方法契约,然后传递实现类进行特定处理,请注意:为了和后边所说的匿名类区分,我们此处定义的实现类都具有固定的ClassName。方法关联在类上,进行的方法调用。这是当前的通用且常用的处理形式。但针对集合,我们有更优秀的处理方式。

总结:

  1. Lambda 表达式之方法传递,并不是直接传递的方法定义,而是传递的实例类,方法附加在实例类进行的调用。
  2. 为了方便统一调用,我们会定义接口,使用面向接口编程形式,将各个实现类的入参、出参固定,方便统一处理。
  3. 方法传递是当前常用的处理方式,也是目前最为通用的处理方式。但针对集合来说,我们有更适应人阅读的lambda。
  4. 方法传递,并不是传递方法定义。最起码当前这种示例不是这种。