函数式接口是什么
定义:
有且仅有一个抽象方法的接口(不包括默认方法、静态方法以及对Object方法的重写)
大家对函数式接口的认识应该都来自于Java8的Stream API,比如Predicate、Function,借助这些函数式接口,Stream才能写出一个个骚操作:
对于Stream流大家常用的方法有哪些?

看着这些常用方法,入参都是Function相关接口函数
public class StreamTest { public static void main(String[] args) { List<User> userList = Lists.newArrayList(); userList.add(new User(1L, "彼得", 18)); userList.add(new User(2L, "鲍勃", 19)); userList.add(new User(3L, "威廉 userList.stream() .filter(user -> user.getAge() > 18) .map(User::getName) .forEach(System.out::println); } }
点进filter方法,你会发现它的参数就是一个函数式接口Predicate:

我们可以从中得到启发:
函数式接口不同于以往的普通接口,它最大的作用其实是为了支持行为参数传递,比如传递Lambda、方法引用、函数式接口对应的实例对象等。
总结一句话,Function函数作为方法入参,核心原因就是把具体业务逻辑交由外层处理。使方法只处理核心逻辑,从而减少业务逻辑对方法的侵入性,使代码更加简洁优雅,易于维护。
例如map(Function<? super T, ? extends R> mapper)
方法,就是把集合中单个item对象的处理逻辑交由外部逻辑处理。 map方法内只需获取结果进行核心逻辑处理。
四大基本函数式接口
是 java.util.function 包下最基本的四个函数式接口。
- Function
Function<T, R> 是 Java 8 中的一个函数式接口,用于表示接受一个输入参数 T,并返回一个结果 R 的函数。Function接口中有一个抽象方法apply,用于定义函数的逻辑。Function接口通常用于将数据进行转换、映射或者执行某种转换操作。

Function 接口的 apply 方法,就是让你传入一个参数,返回一个值。
并且在泛型中体现了 传入 和 返回 的参数类型。
实例1:
import java.util.function.Function; public class Function_Demo { public static void main(String[] args) { Function<String,String> function = new Function<String, String>() { @Override public String apply(String s) { return s; } }; System.out.println(function.apply("Sky")); //lambda 表达式写法: function = ((str)->{ return str;}); System.out.println(function.apply("Song")); } } 打印结果: Sky Song
实例2:
public static void main(String[] args) { List<Plan> planList = new ArrayList<>(Collections.emptyList()); planList.add(new Plan(1L,"SUCCEED")); planList.add(new Plan(2L,"FAIL")); // map方法入参,需要传入一个Function函数。item -> item.getPlanNo())写法属于 // Function函数规范。item表示入参,item.getPlanNo()表示返参 Set<Long> planNoList1 = planList.stream().map(item -> item.getPlanNo()).collect(Collectors.toSet()); // 简写 Set<Long> planNoList2 = planList.stream().map(Plan::getPlanNo).collect(Collectors.toSet()); } static class Plan{ private Long planNo; private String planStatus; public Plan(Long planNo, String planStatus){ this.planNo = planNo; this.planStatus = planStatus; } public Long getPlanNo() { return planNo; } public Plan setPlanNo(Long planNo) { this.planNo = planNo; return this; } public String getPlanStatus() { return planStatus; } public Plan setPlanStatus(String planStatus) { this.planStatus = planStatus; return this; } }
- Predicate

Predicate 接口的 test 方法就是传入一个参数,返回一个 boolean 值。
实例:
import java.util.function.Predicate; public class Predicate_demo { public static void main(String[] args) { Predicate<Integer> predicate =new Predicate<Integer>() { @Override public boolean test(Integer integer) { return 0 != integer; } }; System.out.println(predicate.test(0)); //lambda 表达式写法: predicate = (integer -> {return 0 != integer;}); System.out.println(predicate.test(1)); } } 打印结果: false true
- Consumer

Consumer 接口的 accept 方法就是 传入一个参数,但是不返回值。
是的,传进去的值被消费了,顾名思义!!
实例1:
import java.util.function.Consumer; public class Consumer_Demo { public static void main(String[] args) { Consumer<String> consumer = new Consumer<String>() { @Override public void accept(String s) { System.out.println("消费:"+s); } }; consumer.accept("cake"); //lambda 表达式写法: consumer = (str)->{ System.out.println("消费:"+str); }; consumer.accept("money"); } } 打印结果: 消费:cake 消费:money
实例2:
public static void main(String[] args) { List<Plan> planList = new ArrayList<>(); planList.add(new Plan(1L,"SUCCEED")); planList.add(new Plan(2L,"FAIL")); planList.add(new Plan(3L,"SUCCEED")); List<Long> planNo = new ArrayList<>(); planList.stream() .filter(plan -> "SUCCEED".equals(plan.getPlanStatus())) // forEarch方法入参,需要传入一个Consumer函数。plan -> planNo.add(plan.getPlanNo())符合函数规范。 plan表示入参。 .forEach(plan -> planNo.add(plan.getPlanNo())); System.out.println("count:" + planNo.size()); }
- Supplier
有了 消费者 自然要有 生产者!

Supplier 接口的 get 方法就是不用往里传参数,就能返回一个值。
实例:
import java.util.function.Supplier; public class Supplier_Demo { public static void main(String[] args) { Supplier<String> supplier = new Supplier<String>() { @Override public String get() { return "提供:Sky"; } }; System.out.println(supplier.get()); //lambda 表达式写法: supplier = ()->{return "提供:Song";}; System.out.println(supplier.get()); } } 打印结果: 提供:Sky 提供:Song
- BiFunction函数
与Function区别在于,BiFunction允许传入2个参数,返回一个值。
- BiConsumer函数
与Consumer函数区别在于,BiConsumer函数允许传入2个参数,无返回值。
实际场景Function函数应用
当我了解Stream对Function的函数运用后,我佩服这些源码编写大佬的思路与才华。所以我认为核心是需要学习他们的编码设计思路,在日常编写代码中,真的碰到了类似场景,就可以想起并运用这套思想去设计代码,使得我们代码更加优雅,简洁高效。这也是我们学习源码的初衷。
例如有这样一个场景,我们对接了很多支付渠道,每家对接口参数的加密方式不同。我们可以针对支付渠道设计一个抽象类。在抽象类中定义一个签名方法。具体不同渠道的签名规则以Function函数传入。
抽象类中公共签名方法
/** * 排序并签名 * * @param params 要签名参数 * @param append 待签名字符串追加内容 * @param signName 签名参数 * @param signer 签名函数 * @return 最终签名 */ protected final String sign(Map<String, String> params, String append, String signName, Function<String, String> signer) { String signStr = params.keySet().stream() .filter(key -> StringUtils.hasLength(key) && !key.equals(signName) && StringUtils.hasLength(params.get(key))) .sorted() .map(key -> key + "=" + params.get(key)) .collect(Collectors.joining("&")); signStr += append; if (logger.isDebugEnabled()) logger.debug("待签名字符串:{}", signStr); return signer.apply(signStr); } /** * md5 大写加密 */ protected final String md5Upper(String signStr) { return md5Origin(signStr).toUpperCase(); } /** * md5 加密 */ protected final String md5Origin(String signStr) { return DigestUtils.md5Hex(signStr); } 具体支付渠道进行签名调用 // 调用抽象类中的sign签名方法 String sign = sign(params, "&key=" + "私钥字符串", "sign" // 这里不同渠道类中也可自定义加密规则 , this::md5Origin); // 另外一种写法 String sign2 = sign(params, "&key=" + "私钥字符串", "sign" , (signStr) -> DigestUtils.md5Hex(signStr));
类似的场景其实有很多,在很多源码使用上,都会把业务逻辑通过函数式入参,交由你来处理。使得源码方法的公用性会更强,代码更加简洁易于维护。