问题
在编写一个API时, 需要查询多张表, 但又需要分页. 所以从哪些库中取, 以及取多少, 需要写判断语句和计算得到. 但编写时发现, 大部分的分页逻辑查询, 不同的仅仅调用的dao层函数不同. 所以想将需要调用的dao层函数, 作为参数传入到封装好的分页函数里.
但可惜的是, Java里并没有函数指针. 不过可以用反射或函数式编程解决该问题.
什么是函数式编程
通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。代码一般情况下是操控数据, 数据并分解, 组合, 转换. 而函数式编程是 操作代码片段 . 好处是能减少代码量, 而且能抽象代码片段中相同的逻辑, 从而更好维护和修改代码.
函数编程特点之一: 允许把函数本身作为参数传入另一个函数,还允许返回一个函数.
将函数作为参数传递的三种方法
基本原理: 通过多态实现.
参数为一个接口类, 通过接口类的不同实现, 从而实现对传递不同的函数.
调用一个接口里的函数, 该函数的逻辑会根据具体实现类变化而变化. 但不变的是参数, 返回值以及函数名.
// 函数式接口, 只有一个方法, 用于传递函数
interface Strategy {
String approach(String msg);
}
// 一个实现Strategy接口的类, 目的是实现该接口的方法
class Soft implements Strategy {
public String approach(String msg) {
return msg.toLowerCase() + "?";
}
}
// 一个与Strategy无关的类
class Unrelated {
static String twice(String msg) {
return msg + " " + msg;
}
}
public class Strategize {
Strategy strategy;
String msg;
Strategize(String msg) {
strategy = new Soft(); // [1]
this.msg = msg;
}
void communicate() {
System.out.println(strategy.approach(msg));
}
void changeStrategy(Strategy strategy) {
this.strategy = strategy;
}
public static void main(String[] args) {
// 一个接口
Strategy[] strategies = {
// Strategy 是一个接口, 使用匿名内部类实现该接口
new Strategy() {
public String approach(String msg) {
return msg.toUpperCase() + "!";
}
},
// 匿名函数 Lambda 表达式
msg -> msg.substring(0, 5)
// 方法引用 Java8后支持,可以发现 approach 方法与 twice 方法都是接受一个String参数, 但 Unrelated 与 Strategy 接口没有任何关系, 如继承, 应用接口等
Unrelated::twice
};
Strategize s = new Strategize("Hello there");
s.communicate();
for(Strategy newStrategy : strategies) {
// 通过接口的不同实现类, 实现将函数作为参数传入, 从而得到不同的结果
s.changeStrategy(newStrategy);
s.communicate();
}
}
}
方法引用
Java8 后支持. 基本构成为
[类型|对象名] :: 方法名称
// 函数式接口
interface Callable {
void call(String s);
}
class Father {
private String name;
// 静态方法
static void find(String msg) {
System.out.println("I find a " + msg);
}
void setName(String name) {
this.name = name;
System.out.println(this.name);
}
}
public class Main {
public static void main(String[] args) {
// 可以视为 将 Father 里的 find函数赋值给 Callable 接口
Callable call = Father::find;
call.call("son");
// 实例化一个类
Father father = new Father();
// 接口方法由实例的setName实现
Callable call_1 = father::setName;
call_1.call("Jack");
}
}
甚至支持构造函数的映射
interface MakeNoArgs {
Dog make();
}
class Dog {
String name;
Dog() {name="Jack";}
}
public static void main(String[] args) {
MakeNoArgs mna = Dog::new; // MakeNoArgs 接口的 make 方法由 Dog 的实例化方法实现
Dog myDog = mna.make();
}
@FunctionalInterface 函数式接口
函数式接口: 该接口有且仅有一个抽象方法
函数式接口仍是一个接口, 只不过在接口的基础上, 添加一个条件而已, 即有且仅有一个抽象方法. 除此之外可以将其当为普通接口使用
标注于函数式接口, 告知编译器检查实现该接口的方法只有一个
==该注释是可选的==
接口只包含一个抽象方法,可称该接口为函数式接口
上面的接口都应该添加该注释, 告知编译器在编译器检查这些接口是否只有一个抽象方法
// 编译失败, 函数式接口只应有一个抽象方法, 而该接口有两个抽象方法!
@FunctionalInterface
interface NotFunctional {
// 两个抽象方法, 不允许
String goodbye(String arg);
String hello(String arg);
}
注意
- 有默认实现的方法不算做一个抽象方法
如果一个接口声明了一个覆盖java.lang.Object的一个公共方法的抽象方法,不计入该接口的抽象方法数
因为该接口的任何实现都有一个来自java.lang.Object或其他地方的实现
@FunctionalInterface
interface NotFunctional {
String goodbye(String arg);
// 不算做一个抽象方法, NotFunctional类仍算作只有一个抽象方法
default String hello(String arg) {
System.out.print(arg);
return arg;
}
}
在 Java 中有哪些是函数式接口呢?
最常见的有
- Comparator 接口
- Runnable 接口
例子
要求输入两个整数, 与一个函数 (该函数接受两个整数, 返回一个整数).
两个整数在执行该函数时, 两个整数必须各自 - 100 后执行传入的函数, 并将其结果返回
@FunctionalInterface
interface Transform {
int transform(int num1, int num2);
}
public class Main {
public static int doTransform(int num1, int num2, Transform tra) {
num1 -= 100;
num2 -= 100;
return tra.transform(num1, num2);
}
public static int add(int num1, int num2) {
return num1 + num2;
}
public static int multiple(int num1, int num2) {
return num1 * num2;
}
public static void main(String[] args) {
int num1 = 200;
int num2 = 200;
System.out.println(doTransform(num1, num2, Main::add));
System.out.println(doTransform(num1, num2, Main::multiple));
}
}
// 或者用Java内置的函数式接口
public class Main {
public static int doTransform(int num1, int num2,BiFunction biFunction) {
num1 -= 100;
num2 -= 100;
return biFunction.apply(num1, num2);
}
public static int add(int num1, int num2) {
return num1 + num2;
}
public static int multiple(int num1, int num2) {
return num1 * num2;
}
public static void main(String[] args) {
int num1 = 200;
int num2 = 200;
System.out.println(doTransform(num1, num2, Main::add));
System.out.println(doTransform(num1, num2, Main::multiple));
}
}
想法
让我想到 Filter 拦截器的作用. 对一个 request 进行处理, 虽然中间逻辑的不同, 但预处理和检查都是相同. 若没有拦截器. 只要中间逻辑不同, 预处理和检查的代码就要重复一次. 而拦截器的存在, 让我们相同的逻辑只用写一次. 代码量更少了, 也更好维护和修改. 而上述代码中也是同理, - 100 相当于预处理, 而 add, multiple 相当于中间的不同逻辑. 使用函数式接口, 可以帮助统一逻辑, 实现代码重用.