jdk8函数式编程最佳实践(代码片段)

dm_vincent dm_vincent     2022-12-03     466

关键词:

文章导航

JDK 8 函数式编程最佳实践

1. Lambda表达式的重要接口

1.1 新增的函数接口

主要指的是java.util.function包中的函数接口。

函数式接口:只含一个方法定义的接口。函数式接口能够定义一个Lambda表达式的类型。Lambda表达式实际是将特定形式的入参和返回值进行了抽象。

主要的抽象类型:

  1. Consumer:有一个入参,没有返回值
  2. Supplier:没有入参,有返回值
  3. Function:有一个入参,也有返回值

由此衍生来的常用抽象类型:

  1. Predicate:有一个入参,返回值为Boolean
  2. BiConsumer:有两个入参,没有返回值
  3. BiFunction:有两个入参,有返回值
  4. UnaryOperator:Function的特殊情况,入参和返回值类型相同
  5. BinaryOperator:BiFunction的特殊情况,两个入参和返回值的类型都相同
  6. Double,Integer以及Long的类型为primitive的各种特殊形式

根据这个定义,JDK中原来含有一个方法定义的接口都能够被上述的函数式接口进行抽象。

1.1 无参数,无返回值类型接口

典型的比如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");

1.2 无参数,有返回值类型接口

典型的比如Callable接口,它的定义实际上是一个Supplier类型。

1.3 有参数,也有返回值的类型接口

比如Comparator接口,它可以被一个ToIntBiFunction<T, T>抽象。所以对集合进行排序的操作可以这样写:

Collections.sort(list, (elem1, elem2) -> 
  return elem1.compareTo(elem2);
);

2. 方法引用

几种形式的引用列举如下。

2.1 静态方法引用

private static void testStaticRunnable() 
    System.out.println("runnable in static");

// execute方法接受一个Runnable实例作为参数,Runnable接口的定义符合testStaticRunnable方法的定义形式,因此可以将该方法引用成Lambda表达式传入到execute方法中
threadPool.execute(Test::testStaticRunnable);

2.2 实例方法引用

引用实例方法的规则:实例本身会作为第一个参数,实例方法接受的参数作为后续参数,然后返回值作为最后一个参数,就像下面这样:

BiFunction endsWith = String::endsWith;

endsWith方法接受一个String作为参数,返回一个Boolean作为返回值,因此被定义为一个BiFunction类型。

2.3 构造方法引用

构造方法本质上也是一个接受指定类型参数(或者不接受任何参数)的方法,只不过返回值是该类型的一个实例,因此可以这样来引用:

// 抽象为接受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;
    

3. 使用Lambda表达式完成集合的规约操作

规约操作分为下面两种,通常的操作都可以通过Collectors这个工具类完成。

3.1 成为一个单值类型

比如:

  • Collectors.joining,用来做字符串拼接
  • Collectors.counting,用来统计集合数量

3.2 成为一个集合类型

比如常用的:

  • Collectors.toList,将Stream转换成一个集合类型,集合实现是ArrayList,如果不想使用ArrayList的话,可以使用Collectors.toCollection
  • Collectors.partitioningBy,接受一个Predicate类型的表达式,将一个集合按True/False作为键值分割成两个集合
  • Collectors.groupingBy,partitioningBy的加强版,可以自定义Key
  • Collectors.toMap,终极版本,需要接受一个keyMapper和valueMapper作为参数,它们都是Function类型的抽象形式

更多的Collector实现可以参看Collectors工具类。

3.3 stream和parallelStream

分别代表了串行执行模式和并行执行模式。当使用并行执行模式时,底层使用的是JDK7中引入的Forn/Join Framework。
在我们的代码中使用parallelStream时并不会提示PMD问题。使用的时候注意一个点:ThreadLocal不要在parallelStream的执行逻辑中使用,因为执行线程并不是当前线程。
另外,由于ParallelStream共用一个Fork/Join线程池,因此会需要避免向其中提交耗时较长的任务。

其实也不建议在生产代码中使用parallelStream,会引发不可预料的后果。还是老老实实地使用线程池更加靠谱。

4. Optional的使用

Optional对于需要判空的场景比较实用,能够有效地减少不必要的判空分支。
我们通过一个场景来解释如何较好地使用Optional。

在通过OPENAPI对外提供服务的场景中:
我们每个对外提供服务的接口可以理解成一个业务场景,每个业务场景在系统内部会有一组错误码,每个内部错误码通过OPENAPI返回给外部的错误码又各不相同。

所以,这涉及到三个层次:

  1. 业务场景
  2. 系统内错误码
  3. 外部错误码

反应到代码中,可以作如下抽象:

/**
 * 业务场景到异常类型的映射关系
 */
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调用的条件是openApiErrorConfig不为null
  • 第二次map调用的条件是OpenApiErrorConfig::getScenarioToErrorMapping不为null
  • 第三次map调用的条件是mappings.get(scene)不为null

以上任何一次map调用如果返回了null,那么就会返回orElse里面的defaultSystemError,即全局兜底的一个错误码。

函数式编程的java编码实践:利用惰性写出高性能且抽象的代码(代码片段)

简介: 本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对Java8有些许了解即可。作者|悬衡来源|阿里技术公众号本文会以惰性加载为例一步步介绍函数式编程... 查看详情

函数式编程的java编码实践:利用惰性写出高性能且抽象的代码(代码片段)

简介: 本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对Java8有些许了解即可。作者|悬衡来源|阿里技术公众号本文会以惰性加载为例一步步介绍函数式编程... 查看详情

函数式编程java函数式编程学习(代码片段)

函数式编程-Stream流函数式编程思想概述面向对象思想关注的是用什么对象完成什么事情,而函数式编程思想就类似于数学中的函数,主要关注的是对数据进行了什么操作优点代码简洁,开发快;接近自然语言࿰... 查看详情

函数式编程java函数式编程学习(代码片段)

函数式编程-Stream流函数式编程思想概述面向对象思想关注的是用什么对象完成什么事情,而函数式编程思想就类似于数学中的函数,主要关注的是对数据进行了什么操作优点代码简洁,开发快;接近自然语言࿰... 查看详情

jdk8新特性(代码片段)

主要内容自定义函数式接口函数式编程常用函数式接口Stream流方法引用学习目标能够使用@FunctionalInterface注解能够自定义无参无返回函数式接口能够自定义有参有返回函数式接口能够理解Lambda延迟执行的特点能够使用Lambda作为方... 查看详情

javascript函数式编程(代码片段)

JavaScript函数式编程(一) JavaScript函数式编程(二)在第二篇文章里,我们介绍了 Maybe、Either、IO 等几种常见的Functor,或许很多看完第二篇文章的人都会有疑惑:『这些东西有什么卵用?』事实上,如果只是为了学... 查看详情

深入理解函数式编程(下)(代码片段)

函数式编程是一种历史悠久的编程范式。作为演算法,它的历史可以追溯到现代计算机诞生之前的λ演算,本文希望带大家快速了解函数式编程的历史、基础技术、重要特性和实践法则。在内容层面,主要使用JavaScript... 查看详情

lambda表达式(代码片段)

...要新特性之一——Lambda表达式。在JDK8之前,Java是不支持函数式编程的,所谓的函数编程,即可理解是将一个函数(也称为“行为”)作为一个参数进行传递。通常我们提及得更多的是面向对象编程,面向对象编程是对数据的抽... 查看详情

深入理解函数式编程(下)(代码片段)

总第540篇2022年第057篇函数式编程是一种历史悠久的编程范式。作为演算法,它的历史可以追溯到现代计算机诞生之前的λ演算,本文希望带大家快速了解函数式编程的历史、基础技术、重要特性和实践法则。在内容层面&#... 查看详情

lambda表达式最佳实践(代码片段)

简介Lambda表达式java8引入的函数式编程框架。之前的文章中我们也讲过Lambda表达式的基本用法。本文将会在之前的文章基础上更加详细的讲解Lambda表达式在实际应用中的最佳实践经验。优先使用标准Functional接口之前的文章我们讲... 查看详情

jdk8新特性之函数式接口(代码片段)

目录函数式接口1.函数式接口的由来2.函数式接口介绍2.1Supplier2.2Consumer2.3Function2.4Predicate函数式接口1.函数式接口的由来我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法... 查看详情

jdk8系列之使用function函数式接口实现回调(代码片段)

1.知识回顾写文章之前,还是先补充一下函数式接口的知识。什么是函数式接口(FunctionalInterfaces)?函数式接口是jdk8的新特性之一,函数式接口是只包含一个抽象方法声明的接口。按分类主要分为四大接口类... 查看详情

jdk8系列之使用function函数式接口实现回调(代码片段)

1.知识回顾写文章之前,还是先补充一下函数式接口的知识。什么是函数式接口(FunctionalInterfaces)?函数式接口是jdk8的新特性之一,函数式接口是只包含一个抽象方法声明的接口。按分类主要分为四大接口类... 查看详情

php小部件最佳实践:使用函数(代码片段)

查看详情

spring事务使用最佳实践(代码片段)

目录1Spring事务最佳实践1.1、Spring事务传播机制1.2、隔离规则1.3、Spring事务实现方式1.3.1、编程式事务实现1.3.2、声明式事务实现1.3.3、Spring声明式事务使用注意事项2、事务问题治理2.1、大事物的危害2.1.1事务问题原因分类2.1.2、大... 查看详情

《jdk8新特性专题》-03函数式接口(代码片段)

文章目录1.函数式接口的由来2.函数式接口介绍2.1.Supplier2.2.Consumer2.3.Function2.4.Predicate1.函数式接口的由来我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象... 查看详情

什么是函数式编程(代码片段)

主要参考的是《Java函数式编程》高清华译版本定义函数式编程其实就是编写非故意副作用的程序。课外知识——什么是函数函数简单的说就是从A(定义域)到B(值域)的一个映射过程。当然具体的函数还有各种限制,具体见链接。... 查看详情

函数式编程--为什么要学习函数式编程?(代码片段)

函数式编程(FunctionalProgramming,FP)什么是函数式编程?通过纯函数来实现一些细粒度的函数,然后把这些细粒度的函数组合成功能更强大的函数,这一过程就是函数式编程,经典函数式编程库:lodash函数式编程是编程范式之一,... 查看详情