工作这么久了,也是最近半年才注意到java.util.function。以前都完全没有使用过这个包,因此当第一次看见这个的时候就比较蒙圈。原谅我的寡闻。
我也是网上百度了一下。
接口
总结来说有四个接口。
Function:通俗理解为具体执行方法的执行体
表示接受一个参数并生成结果的函数。并且核心的方法是apply方法,表示执行。
Consumer:消费者。通俗理解为拿到数据后需要执行的操作。
表示接受单个输入参数但不返回结果的操作。与大多数其他功能接口不同,消费者需要通过另一侧进行操作。
Supplier:供给者。提供数据的一方。
Predicate:用于测试一个方法。返回true或false。
通过这四个接口可以发现,其思想与消息中间件有类似之处。都有消息提供者和消息消费者模式,但是不同的是,这里并不算是异步的,Supplier只是提供了一个存储值的功能。而consumer则是提供了一个执行定义方法的功能。且consumer是void方法。分开使用也可。其实主要还是用于抽象统一。
应用
那有没有什么比较好的应用呢?
有。那就是模板模式。
假设纯牛奶和特仑苏的生产工序基本一样,但是某个来源和关键步骤是不同的。那么应该是什么样的代码呢?
首先要定义一个模板类。
package com.xq.traditonal.impl;
/**
* 牛奶制作抽象类
*/
public abstract class AbstractProcess {
// 牛奶工序流程
public String execute(){
String f = commonHandle();
String sec = specialHandle(f);
return assemble(sec);
}
// 组装处理
private String assemble(String sec) {
return sec + " 组装完成\n";
}
// 公共处理
private String commonHandle() {
return "通用处理一。\n" ;
}
// 特殊处理
abstract String specialHandle(String source);
}
然后继承模板类定义各自的具体逻辑。
package com.xq.traditonal.impl;
/**
* 纯牛奶
*/
public class PureMilk extends AbstractProcess{
private String name;
public PureMilk() {
name = "纯牛奶";
}
@Override
String specialHandle(String source) {
return source + name + "杀菌+消毒+防腐剂。\n";
}
}
package com.xq.traditonal.impl;
/**
* 特仑苏
*/
public class Terentsu extends AbstractProcess{
private String name;
public Terentsu() {
name = "特仑苏";
}
@Override
String specialHandle(String source) {
return source + name + "杀菌+消毒\n";
}
}
最后在测试一下:
package com.xq.traditonal;
import com.xq.traditonal.impl.PureMilk;
import com.xq.traditonal.impl.Terentsu;
public class TraditionalTest {
public static void main(String[] args) {
PureMilk pureMilk = new PureMilk();
System.out.println(pureMilk.execute());
Terentsu terentsu = new Terentsu();
System.out.println(terentsu.execute());
}
}
结果如下:
现在只有 特仑苏和纯牛奶。市面上的牛奶品种可是要非常多的,要是让你实现十几种,岂不是要多加10多个文件!
现在我们改成使用suppiler和consumer的方式实现。
这种方式要简洁很多!
我们需要将抽象类改造一下。
package com.xq.func.impl;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* 流程处理
*/
public class FuncProcess {
// 牛奶工序流程
private StringBuilder execute(Supplier<StringBuilder> supplier, Consumer<StringBuilder> consumer){
StringBuilder f = commonHandle();
StringBuilder stringBuilder = f.append(supplier.get());
consumer.accept(stringBuilder);
return assemble(stringBuilder);
}
// 组装处理
private StringBuilder assemble(StringBuilder sec) {
return sec .append(" 组装完成\n");
}
// 公共处理
private StringBuilder commonHandle() {
return new StringBuilder("通用处理一。\n") ;
}
// 纯牛奶
public String pure() {
return execute(() ->new StringBuilder("纯牛奶"), c -> c.append("杀菌+消毒+防腐剂。\n")).toString();
}
// 特仑苏
public String terensu() {
return execute(() ->new StringBuilder("特仑苏"), c -> c.append("杀菌+消毒\n")).toString();
}
}
可以看见我们将execute的模板定义好后直接使用匿名函数的形式填充了模板所需要的方法。
Supplier充当了牛奶来源(纯牛奶or特仑苏)。
Consumer充当了特殊处理。
如果有多个特殊处理怎么办?多加几个consumer作为入参就行了。Supplier同理。
写如下测试用例:
package com.xq.func;
import com.xq.func.impl.FuncProcess;
public class FuncTest {
public static void main(String[] args) {
FuncProcess funcProcess = new FuncProcess();
System.out.println(funcProcess.pure());
System.out.println(funcProcess.terensu());
}
}
结果:
当然,这个明显有弊端。 最大的弊端就是consumer没有返回值,所以在正常的模板模式中这些都不限制,但是在第二种需要传引用方便修改,显得有点不雅观,别扭,最好还是用于comsumer处理不需要返回的情况,比较合适。
结论
考虑的情况。
实现种类很多且都相对简单。采用suppllier,consumer方式。
想要灵活一点且种类并不多的情况。采用传统的模板模式更容易理解。