Java 的函数式编程: 接口, Lambda表达式与方法引用

一. 什么是函数式编程?

函数式编程是一种编程方式, 它将电脑运算视为函数的计算, 这意味着函数在编程过程中处于首要地位, 相对于指令任何实现包括流程控制都可以是函数. 其主要思想是把运算过程尽量写成函数间的嵌套. 相较于 ifelsewhile 这样的过程控制指令, 函数式编程更强调函数的计算, 也更侧重于帮助程序员把重点放在要做的事情上, 很少会(不是不会)涉及到不做什么. 而指令集更偏向于对工作的流程的描述, 处理过程应该是怎样的流程, 碰到了其他一种或多种情况要怎么办. 其实质区别在于函数式更关注结果的达成, 而指令式变成更偏向于对实现的过程的清晰描述, 当然两者一般都能完成所要达成的目标. 两者不能说谁更好, 都是有效的工作流程的集合方式, 各有优劣.

二. Java 中函数式编程的实现

Java 自 1.8 版本开始支持函数式编程, 其主要的实现分为两个方面:

  1. 一方面增加了一些函数式接口(@FunctionalInterface), 这应该是为了兼容既有的面向对象编程的体系
  2. 另一方面实现了自己的 lambda 表达式语法支持, 保证了函数式编程的便捷性 (即, 不需要为每个函数式接口实现具体的类再调用).

三. 常见的函数式编程接口

函数式编程接口常见的有以下几种, 其主要差异在于, 参数数量, 是否返回结果, 以及返回结果的类型.

1. Function 接口

Function 是种接受单个参数并返回结果的函数式接口.

一个典型的 Function 的实例化示例如下:

Function<String, Integer> strLen = new Function<>() {

    @Override
    public Integer apply(String t) {
        return t.length();
    }

};
System.out.println("Function 示例, 字符串长度: " + strLen.apply("Hello"));

其中 String 泛型表示输入参数的类型, 表示 Function 接口接受 String 类型的参数, 而 Integer 泛型是返回结果的类型.

而 apply 就是 Function 接口的主体函数方法, 也就是要使 Function 有效参与函数式编程的必须实现的方法.

有关 Function 更详细的用法, 请参照下表及其中的链接:

序号类型名称说明
1方法Function.andThen()

生成一个组合函数, 该函数首先将当前 Function 作为其输入, 然后将 after 函数应用于输入后产生的结果.

2方法Function.apply()

使用提供的参数应用函数并返回结果

3方法Function.compose()

生成一个组合函数, 该函数首先将 before 函数应用于当前函数的输入, 然后将当前函数应用于结果.

4方法Function.identity()

生成一个总是返回输入的函数

2. Consumer 接口

Consumer 是种接受单个参数但不返回结果的函数式接口.

一个典型的 Consumer 实例化示例如下:

Consumer<String> sayHello = new Consumer<>() {
    @Override
    public void accept(String t) {
        System.out.println("Consumer 示例: 你好, " + t);
    }
};
sayHello.accept("21yi");

其中 String 泛型表示输入参数的类型, 表示 Consumer 接口接受 String 类型的参数.

而 accept 是 Consumer  接口的主体函数方法.

有关 Consumer 更详细的用法, 请参照下表及其中的链接:

序号类型名称说明
1方法Consumer.accept()

用给定参数执行该 Consumer.

2方法Consumer.andThen()

and then 就是然后的意思, 顾名思义, 这是在当前 Consumer 后添加操作的方法. 该方法生成一个组合的 Consumer, 它依次执行此 Consumer 和 after 参数提供的操作.

3. Supplier 接口

Supplier 是种无参数返回结果的函数式接口.

一个典型的 Supplier 实例化示例如下:

Supplier<Integer> randSupplier = new Supplier<>() {
    @Override
    public Integer get() {
        return (int) (Math.random() * 100);
    }
};
System.out.println("Supplier 示例, 1-100随机数: " + randSupplier.get());

其中 String 泛型表示返回结果的类型.

而 get 是 Supplier 接口的主体函数方法.

有关 Supplier 更详细的用法, 请参照下表及其中的链接:

序号类型名称说明
1方法Supplier.get()

获取生成器的结果

4. Predicate 接口

Predicate 是种接受单个参数的函数式接口, 所谓断言就是判断是否, 说明该函数式接口返回布尔值

一个典型的 Predicate 实例化示例如下:

Predicate<Double> isDoubleMax = new Predicate<>() {
    @Override
    public boolean test(Double d) {
        return d.equals(Double.MAX_VALUE);
    }
};
System.out.println("Predicate 示例, 是否 Double 的最大值? " + isDoubleMax.test(Double.MAX_VALUE));

其中 Double 泛型表示输入参数的类型, 表示 Predicate 接口接受 Double 类型的参数, 而返回结果是个布尔值.

而 test 就是 Predicate 接口的主体函数方法.

有关 Predicate 更详细的用法, 请参照下表及其中的链接:

序号类型名称说明
1方法Predicate.and()

生成一个组合断言, 表示该断言和另一个断言的短路逻辑与(AND).

2方法Predicate.isEqual()

生成一个断言, 该断言根据 Objects.equals(Object, Object) 测试两个参数是否相等.

3方法Predicate.negate()

获取一个否定当前断言的断言

4方法Predicate.or()

生成一个组合断言, 表示该断言和另一个断言的短路逻辑或(OR).

5方法Predicate.test()

用指定参数执行断言

5. UnaryOperator 接口

UnaryOperator 是种一元运算符函数接口. 所谓一元运算是指输入和返回的一元性, 详细说来就是对单个参数输入进行运算并返回与输入相同类型的结果.

一个典型的 UnaryOperator 实例化示例如下:

UnaryOperator<String> maskTel = new UnaryOperator<>() {
    @Override
    public String apply(String tel) {
        if(tel.length() < 8) {
            return tel;
        }
        return tel.substring(0, 2) + "****" + tel.substring(6, tel.length());
    }
};
System.out.println("UnaryOperator 示例, 掩盖电话: " + maskTel.apply("1234567890"));

其中 String 泛型表示输入参数的类型, 表示 Function 接口接受 String 类型的参数, 也是返回结果的类型.

而 apply 就是 UnaryOperator 接口的主体函数方法.

有关 UnaryOperator 更详细的用法, 请参照下表及其中的链接:

序号类型名称说明
1方法UnaryOperator.identity()

获取一个始终返回其输入参数的一元运算符

6. BiConsumer 接口

BiConsumer 是种接受两个输入参数但不返回结果的函数式接口.

一个典型的 BiConsumer 实例化示例如下:

BiConsumer<String, Integer> hello = new BiConsumer<>() {
    @Override
    public void accept(String name, Integer age) {
        System.out.println("你好, 我叫" + name + ", 今年 " + age + " 岁了.");
    }
};
System.out.print("BiConsumer 示例");
hello.accept("21yi", 1);

其中 String 泛型表示输入的第1个参数的类型, Integer 泛型表示输入的第2个参数的类型.

而 accept 就是 BiConsumer 接口的主体函数方法.

有关 BiConsumer 更详细的用法, 请参照下表及其中的链接:

序号类型名称说明
1方法BiConsumer.accept()

用给定参数执行该 BiConsumer.

2方法BiConsumer.andThen()

and then 就是然后的意思, 顾名思义, 这是在当前 BiConsumer 后添加操作的方法. 该方法生成一个组合的 BiConsumer, 它依次执行当前 BiConsumer 的操作, after 作为参数提供的操作.

7. BiFunction 接口

BiFunction 是种接受两个参数并返回结果的函数式接口. 

一个典型的 BiFunction 实例化示例如下:

BiFunction<String, Integer, Boolean> expectLength = new BiFunction<>() {
    @Override
    public Boolean apply(String t, Integer u) {
        return t.length() == u;
    }
};
System.out.print("BiFunction 示例, 字符串长度是否符合预期? " + expectLength.apply("21yi", 4));

其中 String 泛型表示输入的第1个参数的类型, Integer 泛型表示输入的第2个参数的类型. Boolean 泛型则表示返回的类型.

而 apply 就是 BiFunction 接口的主体函数方法.

有关 BiFunction 更详细的用法, 请参照下表及其中的链接:

序号类型名称说明
1方法BiFunction.andThen()

生成一个组合函数, 该函数首先将当前 BiFunction 结果作为其输入, 然后应用 after 函数的结果.

2方法BiFunction.apply()

使用提供的两个参数应用到函数并返回结果

四. Java 中 lambda 表达式的实现

在上面的示例中, 我们用到的始终是实例化函数化接口的方式来实现函数式编程, 显然这种方式过于"啰嗦", 无法体现函数式编程的优势. Java 既然支持了函数式编程, 自然也会考虑到这点. Java中实际通过两种方式简化了这个过程:

  1. 通过 lambda 表达式简化
  2. 通过 '::' 实现对方法的"引用"

1. 通过 lambda 表达式简化

在 Java 中, 可以通过类似以下 lambda 表达式来实现各种函数式接口

(arg) -> { return xxx; }

(arg) -> { do something; }

其中圆括号用于指定参数, 如果没有参数则可以直接使用 () 表示, 而花括号用于计算或返回计算结果.

假定我们有一个方法需要接受 Function<String, Integer> 作为参数, 并输出 Function 的执行结果:

private static void applyFunction(Function<String, Integer> func, String p) {
    System.out.println("Function 的执行结果: " + func.apply(p));
}

首先我们显然可以为将一个 Function 示例作为参数传递到该方法中, 同时我们还可以通过 lambda 表达式来实现该过程, 示例如下:

applyFunction(
    (s) -> {return s.length();},
    "21yi");

在该示例中, (s) -> {return s.length();} 表达式就实现了 Function 接口.

其中 s 是参数, 其类型符合定义的要求: String, 花括号中则返回了计算的结果.

2. 通过 '::' 符号实现对方法的 "引用"

如果方法本身已经实现了我们的要求, 再用 lambda 表达式再包次装显然是一种"冗余"写法, 因此 Java 中提供了 '::' 方式对方法的引用, 所谓引用, 不是调用而是先放在这等需要计算的时候再进行计算.

一个比较简单的引用就是对于输出的引用, 示例如下:

private static void acceptConsumer(Consumer<String> consumer, String p) {
    consumer.accept(p);
}
acceptConsumer(System.out::println, "Hello, 21yi");

其中方法 acceptConsumer() 接受一个传递 String 参数的 Consumer 且不返回结果. 符合这种场景最常见的方法就是 System.out.println(), 然而, 我们无法在这里调用, 因此我们采用了引用的方式 System.out::println, 同时我们也能注意到, 引用是不需要圆括号的. 这样就不需要 (s) -> {System.out::println(s)} 这样"啰嗦"的写法了, 虽然这样的表达式也没有语法上的错误.

以上就是 Java 的函数式编程的全部内容了. 最后附上本文中完整的示例代码:

package com.yi21.course;

import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class Yi21FunctionalProgramming {
    public static void main(String[] args) {
        Function<String, Integer> strLen = new Function<>() {

            @Override
            public Integer apply(String t) {
                return t.length();
            }

        };
        System.out.println("Function 示例, 字符串长度: " + strLen.apply("Hello"));

        Consumer<String> sayHello = new Consumer<>() {
            @Override
            public void accept(String t) {
                System.out.println("Consumer 示例: 你好, " + t);
            }
        };
        sayHello.accept("21yi");

        Supplier<Integer> randSupplier = new Supplier<>() {
            @Override
            public Integer get() {
                return (int) (Math.random() * 100);
            }
        };
        System.out.println("Supplier 示例, 1-100随机数: " + randSupplier.get());

        Predicate<Double> isDoubleMax = new Predicate<>() {
            @Override
            public boolean test(Double d) {
                return d.equals(Double.MAX_VALUE);
            }
        };
        System.out.println("Predicate 示例, 是否 Double 的最大值? " + isDoubleMax.test(Double.MAX_VALUE));

        UnaryOperator<String> maskTel = new UnaryOperator<>() {
            @Override
            public String apply(String tel) {
                if(tel.length() < 8) {
                    return tel;
                }
                return tel.substring(0, 2) + "****" + tel.substring(6, tel.length());
            }
        };
        System.out.println("UnaryOperator 示例, 掩盖电话: " + maskTel.apply("1234567890"));

        BiConsumer<String, Integer> hello = new BiConsumer<>() {
            @Override
            public void accept(String name, Integer age) {
                System.out.println("你好, 我叫" + name + ", 今年 " + age + " 岁了.");
            }
        };
        System.out.print("BiConsumer 示例");
        hello.accept("21yi", 1);

        BiFunction<String, Integer, Boolean> expectLength = new BiFunction<>() {
            @Override
            public Boolean apply(String t, Integer u) {
                return t.length() == u;
            }
        };
        System.out.println("BiFunction 示例, 字符串长度是否符合预期? " + expectLength.apply("21yi", 4));

        applyFunction(
            (s) -> {return s.length();},
            "21yi");

        acceptConsumer(System.out::println, "Hello, 21yi");
 
    }

    private static void applyFunction(Function<String, Integer> func, String p) {
        System.out.println("Function 的执行结果: " + func.apply(p));
    }

    private static void acceptConsumer(Consumer<String> consumer, String p) {
        consumer.accept(p);
    }
}

本文首发地址: Java 函数式编程 - Java教程 - 无需氪金,学会编程 & 21教程 21yi.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值