概要
巧妙地使用函数式接口编码提高代码质量,文章举例代码较为潦草,主要为表达设计思想
简述场景
在日常的编码中,经常会遇到对某个集合中的对象进行操作,例如统计,分析,分组等操作。又或者会在某些情况需要对对象的几个属性值进行操作,还有对于某些流程采取不同的策略是需要对各种策略进行选取和实现等很多方面都可以使用Fcuntion,BiFunction,BiConsumer等函数来进行代码的简化
当需要传入参数返回一个参数的时候可以使用Function。传入两个参数一个返回值的时候使用BiFuntion,传入两个参数不需要返回值的时候使用BiConsumer,或者是自定义函数表达式例如:(a,bc)-> a+b+c 将整个表达式作为参数传入到方法中
名词解释
Funtion<T,R>
T作为传入的第一个参数,可以是一个基本数据类型也可以是一个类,同理返回值R一样。
示例如下
Funtion<Integer,String> transTo= b->{
return String.valueOf(b);
};
public String sout(Funtion<Integer,String> transTo,Integer b){
return transTo.apply(b);
}
BiFunction<T,U,R>
类似于Function的参数类型,最后一个R为返回值
示例如下
//传入两个整数值,返回一个double值
BiFunction<Integer,Integer,Double> calculate = (a,b)->{
return double(a/b);
}
public Doublesout(BiFunction<Integer,Integer,Double> calculate, Integer a,Integer b){
return transTo.apply(a,b);
}
BiConsumer<T,U>
可以看出,这个函数时接口类似类似BiFunction 只是没有了返回值R,其使用方式也有一小点不一样,这里的使用方式为accept
BiConsumer<String, Integer> consumer = (str, num) -> System.out.println(str + num);
consumer.accept("The number is: ", 10);
简单的列举了jdk自带的三个函数式接口,当然也可以自定义函数式接口,具体的实现方式会在后续给出
更进一步的运用
栗子1:使用Function来对数据列表进行分组
现在查询出一个小学学生信息的列表,需要统计出各个身高和年龄区间的人占总人数的比例,比如 9岁身高1.2m-1.5的小孩子占整个年级的比例,占整个学校总人数的比例。10岁1.2m-1.6的占比等等各种比例汇算。
先思考一下,如何实现起来优雅而且简单
有一个最基础的实现方式为,对列表进行循环,然后对每一个统计项声明一个int对象来进行统计,最后再使用统计数除于总人数来计算比例
这种方式虽然可行,但是可以想象,其中会有多少个if else。代码将会又臭又长自己都看不下去,再者如果后面又要要求分年级来统计,分班统计,分年龄段统计呢,那代码岂不是要改来改去越改越多吗?
那么这时候使用Funtion就可以很好的去避免这些情况的出现
首先定义一个对象StudenRate,里面来声明各个指标的属性。例如 直接声明 10个num值,作为百分比的占位,并为其添加一个构造方法
然后构造一个Funtion 两个参数 一个是Student 一个是StudentRate
Funtion<Student,StudentRate> rate = d->{
//总数
int num =1;
//比例1人数
int num1 = d.getAge = 9&& d.getheight>=1.2 &&d.getHeight<=1.5? 1:0;
//比例2人数
int num2 = d.getAge = 10&& d.getheight>=1.2 &&d.getHeight<=1.6? 1:0;
//.......
return new StudentRate(num,num1,num2);
}
//这时候rate将区分出当前Studet所处统计范围,然后是对rate的使用
List<Student> list = mapper.query(params);
//总数统计
StudentRate totalRate = new StudentRate();
//分组统计
//这里需要声明一个年级对应的Map,实现自行补充1-6年级
Map<GradeEnum, Map<ShiftEnum, StudentRate>> map = new TreeMap<>();
//声明结果集
Map<String,StudentRate> resultMap = new HashMap<>(8);
list.forEach(student->{
StudentRate rate = rate.apply(student)
//add方法实现为其中的属性值与传入的参数相加,得到统计值 例如 this.num1= this.num1+num1;
totalRate.add(rate);
//例如需要分年级分班统计
GradeEnum grade = student.getGrade();
Optional.ofNullable(student.getShift())
.ifPresent(gradeShift ->{
Map<ShiftEnum, StudentRate> shiftMap = map.get(grade);
if(Objects.nonull(shiftMap)){
StudentRate exist= shiftMap .computeIfAbsent(p,K-> new StudentRate());
exist.add(rate);
//这里将最后的结果放入到声明的结果集中
resultMap.computIfAbsent(p,k->new StudentRate())
}
})
}
)
//然后就是分组后的计算,此处省略
栗子2:使用BiFunction,将列表中的两个对象组装成一个对象
现在需要将上面计算出的结果,和分组名组装成一个新的对象返回
实现如下:
@Data
public Class Domain{
private String name;
private Double rate1;
//属性列表.......
public Domain(String name,StudentRate rate){
this.name= name;
this,rate1= rate.getNum1()/rate.getNUm();
//....
}
}
//分析方法
public <T> List<T> analysisInfo(BiFunction<String,StudentRate,T>,
Map<GradeEnum, Map<ShiftEnum, StudentRate>> map,
Function<S, String> fetchKey)
{
List<Domain> list = new ArrayList<>(map.size());
resultMap.forEach((grade,map)->{
//你的实现
});
return list;
}
这里如何调用只需要执行
this.analysisInfo(Domain::new,map,GradeEnum::getName);
从上的调用可以看出Domain::new中包含了一个构造函数,这个构造函数需要两个参数返回一个domain,正好是一个函数接口的实现
栗子3:
BiConsumer<T,U>
通常会使用BiConsumer来进行一个赋值操作或者是用来作为值传递,这里简单的示范如何使用BiConsumer作为值传递在策略模式中使用
//声明一个策略类
public class Strategy{
//使用一个map来存放我们的策略,String为策略的名称,Biconsumer则是我们策略的实现
privateMap<String,Biconsumer<Integer,Integer>> strategies = new HashMap<>(8);
//声明策略
public StrateGy(){
strategies.put("add", (a, b) -> System.out.println("Result of addition: " + (a + b)));
strategies.put("subtract", (a, b) -> System.out.println("Result of subtraction: " + (a - b)));
}
//策略内部执行
public void execute(String name,int num1,int num2){
BiConsumer<Integer, Integer> strategy = strategies.get(strategyName);
if (strategy != null) {
strategy.accept(num1, num2);
} else {
System.out.println("Unknown strategy: " + strategyName);
}
}
//外部方法调用策略
Strategy strategy = new Stategy();
strategy.execute("add",5,3);
}
在这个示例中,我们使用 BiConsumer 存储了不同的策略,并在 execute 方法中根据传入的策略名称执行相应的操作
小结
可以看出,当需要对数据进行转换,选举,分类,统计,分组后再分组,计算等各种操作的时候,可以使用我们的函数式接口做到很好的优化,虽然刚接触时有点不太好懂,但是掌握熟练后,在编码中也不失为一种很好的编码方式