函数式接口是什么
定义:
有且仅有一个抽象方法的接口(不包括默认方法、静态方法以及对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));
类似的场景其实有很多,在很多源码使用上,都会把业务逻辑通过函数式入参,交由你来处理。使得源码方法的公用性会更强,代码更加简洁易于维护。