主要指的是java.util.function包中的函数接口。
函数式接口:只含一个方法定义的接口。函数式接口能够定义一个Lambda表达式的类型。Lambda表达式实际是将特定形式的入参和返回值进行了抽象。
主要的抽象类型:
由此衍生来的常用抽象类型:
根据这个定义,JDK中原来含有一个方法定义的接口都能够被上述的函数式接口进行抽象。
典型的比如Runnable接口,定义一个Runnable实例不再需要使用匿名类的方式,直接使用Lambda表达式:
Runnable runnable = () -> {
System.out.println("runnable");
};
实际上任何满足无入参,无返回值的方法都可以被引用成为一个Lambda表达式,然后赋值给一个Runnable实例:
Runnable testStatic = Test::testStaticRunnable;
private static void testStaticRunnable() {
System.out.println("runnable in static");
}
典型的比如Callable接口,它的定义实际上是一个Supplier类型。
比如Comparator接口,它可以被一个ToIntBiFunction
Collections.sort(list, (elem1, elem2) -> {
return elem1.compareTo(elem2);
});
几种形式的引用列举如下。
private static void testStaticRunnable() {
System.out.println("runnable in static");
}
// execute方法接受一个Runnable实例作为参数,Runnable接口的定义符合testStaticRunnable方法的定义形式,因此可以将该方法引用成Lambda表达式传入到execute方法中
threadPool.execute(Test::testStaticRunnable);
引用实例方法的规则:实例本身会作为第一个参数,实例方法接受的参数作为后续参数,然后返回值作为最后一个参数,就像下面这样:
BiFunction endsWith = String::endsWith;
endsWith方法接受一个String作为参数,返回一个Boolean作为返回值,因此被定义为一个BiFunction类型。
构造方法本质上也是一个接受指定类型参数(或者不接受任何参数)的方法,只不过返回值是该类型的一个实例,因此可以这样来引用:
// 抽象为接受int作为参数,返回Sample类型的一个Function Lambda表达式
IntFunction sampleNew = Sample::new;
Sample sample = sampleNew.apply(5);
// 类定义
private class Sample {
private int count;
Sample(int count) {
this.count = count;
}
}
规约操作分为下面两种,通常的操作都可以通过Collectors这个工具类完成。
比如:
比如常用的:
更多的Collector实现可以参看Collectors工具类。
分别代表了串行执行模式和并行执行模式。当使用并行执行模式时,底层使用的是JDK7中引入的Forn/Join Framework。
在我们的代码中使用parallelStream时并不会提示PMD问题。使用的时候注意一个点:ThreadLocal不要在parallelStream的执行逻辑中使用,因为执行线程并不是当前线程。
另外,由于ParallelStream共用一个Fork/Join线程池,因此会需要避免向其中提交耗时较长的任务。
其实也不建议在生产代码中使用parallelStream,会引发不可预料的后果。还是老老实实地使用线程池更加靠谱。
Optional对于需要判空的场景比较实用,能够有效地减少不必要的判空分支。
我们通过一个场景来解释如何较好地使用Optional。
在通过OPENAPI对外提供服务的场景中:
我们每个对外提供服务的接口可以理解成一个业务场景,每个业务场景在系统内部会有一组错误码,每个内部错误码通过OPENAPI返回给外部的错误码又各不相同。
所以,这涉及到三个层次:
反应到代码中,可以作如下抽象:
/**
* 业务场景到异常类型的映射关系
*/
private Map<OpenApiServiceScenario, Map<BizResultCode, OpenApiErrorInfo>> scenarioToErrorMapping;
由于在每一层Map的get操作中都可能会得到null,因此判空操作在所难免。
判空所需要的if语句数量和层级的数量成正相关,这对后续分支覆盖率的保证不利。
此时,就可以通过Optional来消除这些仅仅是为了判空的if:
/**
* 全局兜底对外暴露异常
*/
private static OpenApiErrorInfo defaultSystemError;
/**
* 根据场景&业务错误码映射对应的返回错误码
*/
private OpenApiErrorConfig openApiErrorConfig;
/**
* 获取映射后的返回业务错误码
*/
public OpenApiErrorInfo getBizCode(OpenApiServiceScenario scene, BizResultCode errorCode) {
// 根据调用方获取对应所有场景的异常信息
return Optional.ofNullable(openApiErrorConfig)
.map(OpenApiErrorConfig::getScenarioToErrorMapping)
.map((mappings) -> mappings.get(scene))
.map((sceneMapping) -> sceneMapping.get(errorCode)).orElse(defaultSystemError);
}
以上代码中出现了3次map调用:
以上任何一次map调用如果返回了null,那么就会返回orElse里面的defaultSystemError,即全局兜底的一个错误码。