从数学的角度理解函数式接口

Posted q964024886

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从数学的角度理解函数式接口相关的知识,希望对你有一定的参考价值。

函数式接口

函数接口是只有一个抽象方法的接口,此方法是行为的抽象,将行为作为入参,进而在面对对象编程的基础上添加面向函数编程的方式。

接下来,我们以数学的角度来研究一下常见的5个函数式接口,理解了这几个,剩下的也就理解了。
复制代码

1. Function

  • Function接口 -> 接收一个参数并返回一个结果, 类似于一元函数 -> y=f(x)
  • 面向函数编程时要理解"传递行为", 进一步抽象因而变得更灵活
  • 理解: "f.compose(g).apply(x)" -> "y=f(g(x))" 和"f.andThen(g).apply(x)" -> "y=g(f(x))"
 1 public class FunctionTest {
 2 
 3     /**
 4      * Test 01.
 5      */
 6     @Test
 7     public void test01() {
 8 
 9         FunctionTest test = new FunctionTest();
10 
11         System.out.println(test.compute(1, value -> 5 + value));
12         System.out.println(test.compute(3, value -> value * value));
13 
14         System.out.println(test.method1(2));
15         System.out.println(test.method2(2));
16 
17         Function<Integer, Integer> function = value -> value * 2;
18         System.out.println(test.compute(4, function));
19     }
20 
21     /**
22      * Compute int.
23      * 函数式编程写法: 将行为作为入参, 多种行为抽象为一个方法
24      *
25      * @param x        自变量x
26      * @param function 函数执行体
27      * @return 因变量f(x) int
28      */
29     public int compute(int x, Function<Integer, Integer> function) {
30         return function.apply(x);
31     }
32 
33     /**
34      * 传统用法: 将行为写在方法体, 多种行为就得写多个方法
35      *
36      * @param a the a
37      * @return the int
38      */
39     public int method1(int a) {
40         return 5 + a;
41     }
42 
43     /**
44      * Method 2 int.
45      *
46      * @param a the a
47      * @return the int
48      */
49     public int method2(int a) {
50         return a * a;
51     }
52 
53     /* -------------------------------------------------------------- */
54 
55     /**
56      * Test 02.
57      */
58     @Test
59     public void test02() {
60 
61         FunctionTest test = new FunctionTest();
62 
63         /* y=f(g(2))=f(2*2)=f(4)=12 */
64         System.out.println(test.compute(2, value -> value * 3, value -> value * value));
65         /* y=g(f(2))=g(2*3)=g(6)=36 */
66         System.out.println(test.compute2(2, value -> value * 3, value -> value * value));
67     }
68 
69     /**
70      * compose操作
71      *
72      * @param x         x
73      * @param function1 f(x)
74      * @param function2 g(x)
75      * @return the int  y=f(g(x))
76      */
77     public int compute(int x, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
78         return function1.compose(function2).apply(x);
79     }
80 
81     /**
82      * andThen操作
83      *
84      * @param x         x
85      * @param function1 f(x)
86      * @param function2 g(x)
87      * @return the int  y=g(f(x))
88      */
89     public int compute2(int x, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
90         return function1.andThen(function2).apply(x);
91     }
92 }

 

2. BiFunction

  • 函数式接口BiFunction: 接收两个参数, 返回一个值. 类似于二元函数: y=f(x1,x2)
  • 定义BiFunction的行为, 即apply()的实现:(param1,param2) -> { ... }
  • 理解andThen()方法:"f.andThen(g).apply(x1,x2)" -> "y=g(f(x1,x2))". 其中 f为BiFunction, g为Function. 由 x1, x2根据函数 f计算得到中间结果, 再根据函数 g计算得到最终结果
  • 理解为啥 BiFunction没有compose()方法: "f.compose(g).apply(x1,x2)" -> 先执行函数 g得到一个值, 再作为函数 f的入参, 得到最终结果, 但是 f是BiFunction, 需要两个自变量, 产生矛盾, 因此 compose()是不合理的.
  • 在面向对象编程的基础上(对一段执行的逻辑进行抽象, 即方法), 把对象的方法进一步抽象, 即尽量使用函数式接口作为入参, 在使用方法时再定义行为, 优雅, 建议使用.
public class BiFunctionTest {

    /**
     * 测试函数式接口BiFunction的实现方法:
     * R apply(T t, U u);
     */
    @Test
    public void test01() {

        BiFunctionTest test = new BiFunctionTest();

        int add = test.compute(1, 2, Integer::sum);
        System.out.println("1+2 = " + add);
        int sub = test.compute(7, 4, (value1, value2) -> value1 - value2);
        System.out.println("7-4 = " + sub);
        int mul = test.compute(3, 5, (value1, value2) -> value1 * value2);
        System.out.println("3*5 = " + mul);
        int div = test.compute(8, 4, (value1, value2) -> value1 / value2);
        System.out.println("8/4 = " + div);

    }

    /**
     * @param x1         自变量 x1
     * @param x2         自变量 x2
     * @param biFunction 函数体f(x1,x2)
     * @return int 因变量 y=f(x1,x2)
     */
    private int compute(int x1, int x2, BiFunction<Integer, Integer, Integer> biFunction) {
        return biFunction.apply(x1, x2);
    }

    /* -------------------------------------------------------------- */

    /**
     * Test 03.
     */
    @Test
    public void test03() {

        Person person1 = new Person("zhangsan", 20);
        Person person2 = new Person("lisi", 30);
        Person person3 = new Person("wangwu", 40);

        List<Person> persons = Arrays.asList(person1, person2, person3);

        BiFunctionTest test = new BiFunctionTest();

        /* 根据username筛选 <- 将行为写死在方法里(不建议使用) */
        List<Person> personResult1 = test.getPersonsByUsername("zhangsan", persons);
        personResult1.forEach(person -> System.out.println(person.getUsername()));
        System.out.println("-------------------------------------");

        /* 根据年龄筛选 <- 将行为写死在方法里(不建议使用) */
        List<Person> personResult2 = test.getPersonsByAge(20, persons);
        personResult2.forEach(person -> System.out.println(person.getAge()));
        System.out.println("-------------------------------------");

        /* 根据年龄筛选 <- 将行为抽象出来, 因此更加灵活(建议使用) */
        List<Person> personResult3 = test.getPersonsByAge2(20, persons,
                (age, personList) -> personList.stream()
                        .filter(person -> person.getAge() > age)
                        .collect(Collectors.toList()));
        personResult3.forEach(person -> System.out.println(person.getAge()));

        /* 使用方法时再定义行为 */
        List<Person> personResult4 = test.getPersonsByAge2(20, persons,
                (age, personList) -> personList.stream()
                        .filter(person -> person.getAge() <= age)
                        .collect(Collectors.toList()));
        personResult4.forEach(person -> System.out.println(person.getAge()));
    }


    /* -------------------------------------------------------------- */

    /**
     * 测试函数式接口BiFunction的默认方法:
     * default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after)
     */
    @Test
    public void test02() {
        BiFunctionTest test = new BiFunctionTest();

        int result = test.compute2(6, 5, (value1, value2) -> value1 - value2, value -> value * 10);
        System.out.println("(6-5)*10 = " + result);

    }

    /**
     * @param x1         自变量 x1
     * @param x2         自变量 x2
     * @param biFunction 函数体y=f(x1,x2)
     * @param function   函数体y=g(x)
     * @return int 因变量 y=g(f(x1,x2))
     */
    private int compute2(int x1, int x2, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
        return biFunction.andThen(function).apply(x1, x2);
    }

    /**
     * @param username 参数x1
     * @param persons  参数x2
     * @return y
     */
    private List<Person> getPersonsByUsername(String username, List<Person> persons) {
        /* 流式处理 */
        return persons.stream().filter(person -> person.getUsername().equals(username)).
                collect(Collectors.toList());
    }

    /**
     * @param age     参数x1
     * @param persons 参数x2
     * @return y
     */
    private List<Person> getPersonsByAge(int age, List<Person> persons) {
        /* 将函数式接口定义在方法里, 行为里用流式处理 */
        BiFunction<Integer, List<Person>, List<Person>> biFunction =
                (ageOfPerson, personList) -> personList.stream()
                        .filter(person -> person.getAge() > ageOfPerson)
                        .collect(Collectors.toList());
        return biFunction.apply(age, persons);
    }

    /**
     * 将具体行为作为入参, 即对此方法进行进一步抽象
     *
     * @param age        参数x1
     * @param persons    参数x2
     * @param biFunction f(x1,x2)
     * @return y=f(x1,x2)
     */
    private List<Person> getPersonsByAge2(int age, List<Person> persons, BiFunction<Integer, List<Person>, List<Person>> biFunction) {
        return biFunction.apply(age, persons);
    }

}

@Data
@AllArgsConstructor
class Person {

    private String username;

    private int age;

}

 

3. Supplier

  • 函数式接口 Supplier不接受参数, 返回一个值 类似于常数函数: y = a
public class SupplierTest {

    /**
     * 用了函数式接口Supplier之前, 处理 n个行为就得有 n个方法
     */
    @Test
    public void test01() {

        SupplierTest test = new SupplierTest();

        System.out.println(test.getValue1());
        System.out.println(test.getValue2());
        System.out.println(test.getValue3());

    }

    private String getValue1() {
        return "hello world!";
    }

    private int getValue2() {
        int a = 665;
        return a + 1;
    }

    private Object getValue3() {
        return new Object();
    }

    /* -------------------------------------------------------------- */

    /**
     * 用了函数式接口Supplier之后, n个行为, 但是仅定义一个方法
     */
    @Test
    public void test02() {

        SupplierTest test = new SupplierTest();

        System.out.println(test.getValue(() -> "hello world!"));
        System.out.println(test.getValue(() -> {
            int a = 665;
            return a + 1;
        }));
        System.out.println(test.getValue(() -> new Object()));

    }

    /**
     * 用Supplier作为入参, 进一步抽象
     *
     * @param supplier 定义行为
     * @return the value
     */
    private <T> T getValue(Supplier<T> supplier) {
        return supplier.get();
    }
}

 

4. Consumer

  • 函数式接口 Consumer: 接收一个参数, 但是不返回任何结果 理解为 void = f(x)
  • andThen() : void = f(x) & g(x)
public class ConsumerTest {

   /**
    * 传统方式
    */
   @Test
   public void test01() {

       ConsumerTest test = new ConsumerTest();

       test.handle1("qwe");
       test.handle2(5);
       List<Integer> list = Arrays.asList(1, 3, 5);
       test.handle3(list);

   }

   private void handle1(String str) {
       int length;
       length = str.length();
       System.out.println(length);
   }

   private void handle2(int a) {
       System.out.println(a + a);
   }

   private void handle3(List<Integer> lists) {
       System.out.println(lists);
   }

   /* -------------------------------------------------------------- */

   /**
    * 测试函数式接口Consumer
    */
   @Test
   public void test02() {

       ConsumerTest test = new ConsumerTest();

       test.handle("qwe", x -> System.out.println(x.length()));
       test.handle(5, x -> System.out.println(x + x));
       List<Integer> list = Arrays.asList(1, 3, 5);
       test.handle(list, System.out::println);

       /* 测试andThen()方法 */
       test.handle2("twtwtwtwtw", x -> System.out.println(x.substring(1, 3))
               , y -> System.out.println(y.length()));

   }

   /**
    * @param x        参数x
    * @param consumer 行为
    * @param <T>      泛型 T
    */
   private <T> void handle(T x, Consumer<T> consumer) {
       consumer.accept(x);
   }

   /**
    * void = f(x) & g(x)
    *
    * @param x         参数x
    * @param consumer  行为
    * @param consumer2 行为2
    * @param <T>       泛型 T
    */
   private <T> void handle2(T x, Consumer<T> consumer, Consumer<T> consumer2) {
       consumer.andThen(consumer2).accept(x);
   }

}

 

5. Predicate

  • 函数式接口 Predicate 接收一个参数, 返回一个布尔值 类似于 y=f(x), 其中y∈{true, false}
  • 理解 test()、and()、negate()、or()
public class PredicateTest {

    /**
     * 测试函数式接口 Predicate
     */
    @Test
    public void test() {

        PredicateTest test = new PredicateTest();

        List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        System.out.println(test.judge(1, numList::contains));
        System.out.println("--------------- 取奇数 ---------------------------");
        test.judgeList(numList, num -> num % 2 == 1);
        System.out.println("--------------- 取偶数 && 大于5的数 ---------------");
        test.judgeList2(numList, num -> num % 2 == 0, num -> num > 5);
        System.out.println("--------------- 取偶数 ---------------------------");
        test.judgeList3(numList, num -> num % 2 == 1);
        System.out.println("--------------- 取偶数 || 大于5的数 ---------------");
        test.judgeList4(numList, num -> num % 2 == 0, num -> num > 5);

    }

    private Boolean judge(int x, Predicate<Integer> predicate) {
        return predicate.test(x);
    }

    private void judgeList(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer i : list) {
            if (predicate.test(i)) {
                System.out.println(i);
            }
        }
    }

    private void judgeList2(List<Integer> list, Predicate<Integer> predicate1, Predicate<Integer> predicate2) {
        for (Integer i : list) {
            if (predicate1.and(predicate2).test(i)) {
                System.out.println(i);
            }
        }
    }

    private void judgeList3(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer i : list) {
            if (predicate.negate().test(i)) {
                System.out.println(i);
            }
        }
    }

    private void judgeList4(List<Integer> list, Predicate<Integer> predicate1, Predicate<Integer> predicate2) {
        for (Integer i : list) {
            if (predicate1.or(predicate2).test(i)) {
                System.out.println(i);
            }
        }
    }

}

 

在充分理解函数式接口之后,写lambda变得很轻松,然后结合stream流就可以写出优雅的代码了。在业务代码中用stream和lambda真的很爽...

 

以上是关于从数学的角度理解函数式接口的主要内容,如果未能解决你的问题,请参考以下文章

从代码重构角度聊一聊Java8的函数式接口

闭包漫谈(从抽象代数及函数式编程角度)

Java Lambda

Go的魅力, 函数式(柯里化, 闭包, 高阶函数), Python@装饰器, 封装

Linux基础IO篇

第1679其函数式编程浅析