Java函数式编程-Java里的“函数指针”

问题

在编写一个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 相当于中间的不同逻辑. 使用函数式接口, 可以帮助统一逻辑, 实现代码重用.

参考

你可能感兴趣的:(java)