工程经验 - Java 函数参数
一、问题
最近重构后端业务,碰到的一件事是:要将函数作为参数,传递到一个函数里去,函数里的执行流程大概是
- 函数 f1 的两个参数都是函数 f2 和 f3
- 在 f1 里调用 f2 和 f3,f1 内的调用过程为:f2 调用后,它的返回值是一个集合,对该集合的每一个元素执行 f3
总共有两个参数,两个参数都是函数,执行的伪代码大概是:
void f1(f2, f3) {
Param p = new Param();
valueList = f1.execute.(p);
valueList.forEach(f3);
}
这个在 C 语言是件很简单的事情,传递「函数指针」 即可,在 C# 中记得用「委托」即可,但在 Java 中还没做过
二、解决方案
后面看了一下,Java 8 提供了四个常用函数式接口,在 java.util.function
下:
- Consumer(消费型)
- Supplier(供给型)
- Predicate(谓词型)
- Function(功能性)
此时问题中的 f2 使用 Function<T, R>
做,f3 使用 Consumer<T>
来做即可
三、实例
这个实例也是自己 YY 的(公司原因,不大好拿公司业务场景举例 )
3.1 业务场景
假设产品来了需求,要对产品的几个实体做一个展示 show():
- 只打印产品的 id 为 1 ~ 5 的实体
- 现在有三个实体要展示:Book、Cup、Pen
- 三个实体的展示逻辑都不一样
3.2 实现
怎么做?对于 Java Web 工程来说,首先想到的就是在对应的 service 里面去做查询、然后再按照需求对三个实体去写各自展示的业务逻辑
问题是什么?
可以发现它们有一个共有的逻辑:只打印 id 为 1 ~ 5 的实体,那万一产品改了,今天变成 6 ~ 10 ,明天变成 11 - 15 怎么搞?
所以,更好的做法是,把共有的逻辑抽取出来,放到工具类,这样业务只要关注两件事:
- 怎么查
- 怎么打印
这样就增强了开放性了
对应第一章节的描述,可以这么看:
- 工具类为 ShowUtil
- f1 定义为一个展示功能的函数 show()
- f2 定义不同实体的查询方法 query
- f3 定义不同的打印方法 print
- 并且产品规定 show() 只打印 id 为 1 ~ 5 的实体
伪代码为:
public class ShowUtil {
public static void show(query(), print()) {
List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5);
List queryResult = query(ids);
queryResult.forEach(print());
}
}
此时业务方使用 ShowUtil.show()
,专注于自己业务的 query() 和 print() 即可
代码实现
例如,三个实体类 Book、Hat、Pen ,都需要展示,以 Book 和 Hat 为例子,实现一下使用工具类传方法参数的做法
定义实体和数据库查询 ORM 接口(这里使用 mybatis-plus)
Book
@Data
public class Book {
private Integer id;
private String name;
}
@Repository
public interface BookMapper extends BaseMapper<Book> {
}
Hat
@Data
public class Hat {
private Integer id;
private String name;
}
@Repository
public interface HatMapper extends BaseMapper<Hat> {
}
Pen
@Data
public class Pen {
private Integer id;
private String name;
}
ShowUtil :
@UtilityClass
public class ShowUtil {
public static <R> void show(Function<List<Integer>, List<R>> query, Consumer<R> print) {
List<Integer> param = Arrays.asList(1, 2, 3, 4, 5);
List<R> records = query.apply(param);
records.forEach(print);
}
}
此时,业务方为
@Component
public class Application {
@Autowired
BookMapper bookMapper;
@Autowired
HatMapper hatMapper;
public void run() {
// book 展示的业务
ShowUtil.show(new Function<List<Integer>, List<Book>>() {
@Override
public List<Book> apply(List<Integer> integers) {
return bookMapper.selectBatchIds(integers);
}
}, new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println("Book print");
System.out.println(book.getName());
}
});
// Hat 展示的业务
ShowUtil.show(new Function<List<Integer>, List<Hat>>() {
@Override
public List<Hat> apply(List<Integer> integers) {
return hatMapper.selectBatchIds(integers);
}
}, new Consumer<Hat>() {
@Override
public void accept(Hat hat) {
System.out.println("Hat print");
System.out.println(hat.getName());
}
});
}
}
匿名函数比较长,可以采用 lambda 做:
@Component
public class Application {
@Autowired
BookMapper bookMapper;
@Autowired
HatMapper hatMapper;
public void run() {
// book 展示的业务
ShowUtil.show(
integers -> bookMapper.selectBatchIds(integers),
book -> {
System.out.println("Book print");
System.out.println(book.getName());
});
// Hat 展示的业务
ShowUtil.show(integers -> hatMapper.selectBatchIds(integers), hat -> {
System.out.println("Hat print");
System.out.println(hat.getName());
});
}
}
四、总结
Function<T, R>
中,R 为响应值,T 为参数,在本实例中,T 是非泛型直接声明的,因此方法声明就使用 R,但用 T 来做也是可以的