作为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表达式。 如果你还不知道为啥可以这么写?那么我们继续我们的探索之旅。
客户来了需求,需求列表如下:
- 按照weight 进行降序排列
- 按照weight进行分组
- 按照weight进行升序排列
- 其他各种需求
针对当前已知需求,如果我们要实现,假定我们不知道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。方法关联在类上,进行的方法调用。这是当前的通用且常用的处理形式。但针对集合,我们有更优秀的处理方式。
总结:
- Lambda 表达式之方法传递,并不是直接传递的方法定义,而是传递的实例类,方法附加在实例类进行的调用。
- 为了方便统一调用,我们会定义接口,使用面向接口编程形式,将各个实现类的入参、出参固定,方便统一处理。
- 方法传递是当前常用的处理方式,也是目前最为通用的处理方式。但针对集合来说,我们有更适应人阅读的lambda。
- 方法传递,并不是传递方法定义。最起码当前这种示例不是这种。