怎样用Java 8优雅的开发业务

函数式编程

匿名函数

λ演算

流式编程

基本原理

Java中流式编程的基本原理有两点。

  1. 构建流
  2. 数据流转(流水线)
  3. 规约
  1. IntStream.rangeClosed(1, 100) // 1. 构建流
  2. .mapToObj(String::valueOf)// 2. 数据流转(流水线)
  3. .collect(joining()); // 3. 规约

案例

  • 英雄的主位置一共有几类,分别是什么
  1. @Test
  2. fun t1() {
  3. // 英雄的主位置一共有几类,分别是什么
  4. // 映射
  5. val roleMains = heroes.map(Hero::getRoleMain)
  6. // 过滤为空的数据
  7. .filter(Objects::nonNull)
  8. // 去重
  9. .distinct()
  10. println(roleMains.size)
  11. println(roleMains)
  12. }
  1. @Test
  2. public void t1() {
  3. // 英雄的主位置一共有几类,分别是什么
  4. List<String> roleMains = heroes.stream()
  5. // 映射
  6. .map(Hero::getRoleMain)
  7. // 过滤为空的数据
  8. .filter(Objects::nonNull)
  9. // 去重
  10. .distinct()
  11. // 收集列表
  12. .collect(toList());
  13. System.out.println(roleMains.size());
  14. System.out.println(roleMains);
  15. }

  • 英雄按主次位置分组后,输出每个分组有多少英雄,其中:近战英雄有多少位,远程英雄有多少位
  1. @Test
  2. fun t2() {
  3. // 英雄按主次位置分组后,输出每个分组有多少英雄,其中:近战英雄有多少位,远程英雄有多少位
  4. // 主次位置分组的英雄数量
  5. val groupHeroCount = heroes.groupingBy {
  6. Pair.of(it.roleMain, it.roleAssist)
  7. }.eachCount()
  8. // 主次分组后,再按攻击范围分组的英雄数量
  9. val groupThenGroupCount = heroes.groupBy {
  10. Pair.of(it.roleMain, it.roleAssist)
  11. }.map {
  12. val value = it.value.groupingBy(Hero::getAttackRange).eachCount()
  13. Pair.of(it.key, value)
  14. }.associateBy({ it.left }, { it.value })
  15. // 遍历输出
  16. groupThenGroupCount.forEach { (groupKey, groupValue) ->
  17. val groupingCount = groupHeroCount[groupKey]
  18. print("英雄分组key为:$groupKey;英雄数量:$groupingCount;")
  19. groupValue.forEach { (countKey, countValue) ->
  20. print("英雄攻击范围:$countKey;英雄数量:$countValue;")
  21. }
  22. println()
  23. }
  24. }
  1. @Test
  2. public void t2() {
  3. // 英雄按主次位置分组后,输出每个分组有多少英雄,其中:近战英雄有多少位,远程英雄有多少位
  4. // 主次位置分组的英雄数量
  5. Map<Pair<String, String>, Long> groupHeroCount = heroes.stream()
  6. .collect(groupingBy(hero -> Pair.of(hero.getRoleMain(), hero.getRoleAssist()), counting()));
  7. // 主次分组后,再按攻击范围分组的英雄数量
  8. Map<Pair<String, String>, Map<String, Long>> groupThenGroupCount = heroes.stream()
  9. .collect(groupingBy(hero -> Pair.of(hero.getRoleMain(), hero.getRoleAssist()),
  10. groupingBy(Hero::getAttackRange, counting())));
  11. // 遍历输出
  12. groupThenGroupCount.forEach((groupKey, groupValue) -> {
  13. Long groupingCount = groupHeroCount.get(groupKey);
  14. System.out.print("英雄分组key为:" + groupKey + ";英雄数量:" + groupingCount + ";");
  15. groupValue.forEach((countKey, countValue) -> System.out.print("英雄攻击范围:" + countKey + ";英雄数量:" + countValue + ";"));
  16. System.out.println();
  17. });
  18. }

  • 求近战英雄HP初始值的加总
  1. @Test
  2. fun t3() {
  3. // 求近战英雄HP初始值的加总
  4. val sum = heroes.filter { "近战" == it.attackRange }
  5. .map(Hero::getHpStart)
  6. .filter(Objects::nonNull)
  7. .reduce(BigDecimal::add)
  8. println("近战英雄HP初始值的加总为:$sum")
  9. }
  1. @Test
  2. public void t3() {
  3. // 求近战英雄HP初始值的加总
  4. BigDecimal sum = heroes.stream()
  5. .filter(hero -> "近战".equals(hero.getAttackRange()))
  6. .map(Hero::getHpStart)
  7. .filter(Objects::nonNull)
  8. .reduce(BigDecimal.ZERO, BigDecimal::add);
  9. System.out.println("近战英雄HP初始值的加总为:" + sum);
  10. }

  • 通过最小列表收集器获取最小列表
  1. @Test
  2. public void t4() {
  3. // 通过最小列表收集器获取最小列表
  4. List<BigDecimal> minAttackGrowth = heroes.stream()
  5. .map(Hero::getAttackGrowth)
  6. .collect(new MinListCollector<>());
  7. System.out.println(minAttackGrowth);
  8. List<Hero> minHero = heroes.stream()
  9. .collect(new MinListCollector<>());
  10. System.out.println(minHero);
  11. }
  1. import java.util.*;
  2. import java.util.concurrent.atomic.AtomicReference;
  3. import java.util.function.BiConsumer;
  4. import java.util.function.BinaryOperator;
  5. import java.util.function.Function;
  6. import java.util.function.Supplier;
  7. import java.util.stream.Collector;
  8. import java.util.stream.Collectors;
  9. import static java.util.stream.Collector.Characteristics.*;
  10. /**
  11. * 最小列表收集器
  12. *
  13. * @author switch
  14. * @since 2020/8/18
  15. */
  16. public class MinListCollector<T extends Comparable<? super T>> implements Collector<T, List<T>, List<T>> {
  17. /**
  18. * 收集器的特性
  19. *
  20. * @see Characteristics
  21. */
  22. private final static Set<Characteristics> CHARACTERISTICS = Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH));
  23. private final static int ZERO = 0;
  24. /**
  25. * 最小值
  26. */
  27. private final AtomicReference<T> min = new AtomicReference<>();
  28. @Override
  29. public Supplier<List<T>> supplier() {
  30. // supplier参数用于生成结果容器,容器类型为A
  31. return ArrayList::new;
  32. }
  33. @Override
  34. public BiConsumer<List<T>, T> accumulator() {
  35. // accumulator用于消费元素,也就是归纳元素,这里的T就是元素,它会将流中的元素一个一个与结果容器A发生操作
  36. return (list, element) -> {
  37. // 获取最小值
  38. T minValue = min.get();
  39. if (Objects.isNull(minValue)) {
  40. // 第一次比较
  41. list.add(element);
  42. min.set(element);
  43. } else if (element.compareTo(minValue) < ZERO) {
  44. // 发现更小的值
  45. list.clear();
  46. list.add(element);
  47. min.compareAndSet(minValue, element);
  48. } else if (element.compareTo(minValue) == ZERO) {
  49. // 与最小值相等
  50. list.add(element);
  51. }
  52. };
  53. }
  54. @Override
  55. public BinaryOperator<List<T>> combiner() {
  56. // combiner用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
  57. return (left, right) -> {
  58. // 最小值列表合并
  59. List<T> leftList = getMinList(left);
  60. List<T> rightList = getMinList(right);
  61. leftList.addAll(rightList);
  62. return leftList;
  63. };
  64. }
  65. private List<T> getMinList(List<T> list) {
  66. return list.stream()
  67. .filter(element -> element.compareTo(min.get()) == ZERO)
  68. .collect(Collectors.toList());
  69. }
  70. @Override
  71. public Function<List<T>, List<T>> finisher() {
  72. // finisher用于将之前整合完的结果R转换成为A
  73. return Function.identity();
  74. }
  75. @Override
  76. public Set<Characteristics> characteristics() {
  77. // characteristics表示当前Collector的特征值,这是个不可变Set
  78. return CHARACTERISTICS;
  79. }
  80. }

优雅的空处理

  1. import org.junit.Test;
  2. import java.util.Optional;
  3. /**
  4. * @author switch
  5. * @since 2020/8/18
  6. */
  7. public class OptionalTests {
  8. @Test
  9. public void t1() {
  10. // orElse
  11. System.out.println(Optional.ofNullable(null).orElse("张三"));
  12. System.out.println(Optional.ofNullable(null).orElseGet(() -> "李四"));
  13. System.out.println(Optional.ofNullable("王五").orElseThrow(NullPointerException::new));
  14. }
  15. @Test
  16. public void t2() {
  17. // isPresent
  18. Optional<String> name = Optional.ofNullable("张三");
  19. if (name.isPresent()) {
  20. System.out.println(name.get());
  21. }
  22. }
  23. @Test
  24. public void t3() {
  25. // map
  26. Optional<Integer> number = Optional.of("123456").map(Integer::valueOf);
  27. if (number.isPresent()) {
  28. System.out.println(number.get());
  29. }
  30. }
  31. @Test
  32. public void t4() {
  33. // flatMap
  34. Optional<Integer> number = Optional.of("123456").flatMap(s -> Optional.of(Integer.valueOf(s)));
  35. if (number.isPresent()) {
  36. System.out.println(number.get());
  37. }
  38. }
  39. @Test
  40. public void t5() {
  41. // 过滤
  42. String number = "123456";
  43. String filterNumber = Optional.of(number).filter(s -> !s.equals(number)).orElse("654321");
  44. System.out.println(filterNumber);
  45. }
  46. }

新的并发工具类CompletableFuture

单机批处理多线程执行模型

该模型适用于百万级量级的任务。超过千万数据,可以考虑分组,多机器并行执行。

基本流程:

  1. 从数据库获取Id列表
  2. 拆分成n个子Id列表
  3. 通过子Id列表获取关联数据(注意:都需要提供批量查询接口)
  4. 映射到需要处理的Model(提交到CompletableFuture)->处理数据->收集成list)(java 8流式处理)
  5. 收集的list进行join操作
  6. 收集list
模型

模型原理:Stream+CompletableFuture+lambda

简要解释:

  • CompletableFuture是java8提供的一个工具类,主要是用于异步处理流程编排的。
  • Stream是java8提供的一个集合流式处理工具类,主要用于数据的流水线处理。
  • lambda在java中是基于内部匿名类实现的,可以大幅减少重复代码。
  • 总结:在该模型中Stream用于集合流水线处理、CompletableFuture解决异步编排问题(非阻塞)、lambda简化代码。
  • 数据流动
  1. List<List<String>> ->
  2. Stream<List<String>> ->
  3. Stream<List<Model>> ->
  4. Stream<CompletableFuture<List<Model>>> ->
  5. Stream<CompletableFuture<List<映射类型>>> ->
  6. List<CompletableFuture<Void>>
案例
  • ThreadPoolUtil
  1. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  2. import java.util.concurrent.ThreadPoolExecutor;
  3. public final class ThreadPoolUtil {
  4. public static ThreadPoolTaskExecutor getDefaultExecutor(Integer poolSize, Integer maxPoolSize, Integer queueCapacity) {
  5. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  6. executor.setAllowCoreThreadTimeOut(true);
  7. executor.setWaitForTasksToCompleteOnShutdown(true);
  8. executor.setCorePoolSize(poolSize);
  9. executor.setMaxPoolSize(maxPoolSize);
  10. executor.setQueueCapacity(queueCapacity);
  11. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
  12. return executor;
  13. }
  14. }
  • ThreadPoolConfig
  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  4. @Configuration
  5. public class ThreadPoolConfig {
  6. /**
  7. * 计算规则:N(thread) = N(cpu) * U(cpu) * (1 + w/c)
  8. * N(thread):线程池大小
  9. * N(cpu):处理器核数
  10. * U(cpu):期望CPU利用率(该值应该介于0和1之间)
  11. * w/c:是等待时间与计算时间的比率,比如说IO操作即为等待时间,计算处理即为计算时间
  12. */
  13. private static final Integer TASK_POOL_SIZE = 50;
  14. private static final Integer TASK_MAX_POOL_SIZE = 100;
  15. private static final Integer TASK_QUEUE_CAPACITY = 1000;
  16. @Bean("taskExecutor")
  17. public ThreadPoolTaskExecutor taskExecutor() {
  18. return ThreadPoolUtil.getDefaultExecutor(TASK_POOL_SIZE, TASK_MAX_POOL_SIZE, TASK_QUEUE_CAPACITY);
  19. }
  20. }
  • #getFuturesStream
  1. public Stream<CompletableFuture<List<Model>>> getFuturesStream(List<List<String>> idSubLists) {
  2. return idSubLists.stream()
  3. .map(ids ->
  4. CompletableFuture.supplyAsync(() -> modelService.listByIds(ids), taskExecutor)
  5. );
  6. }
  • #standardisation
  1. public void standardisation() {
  2. List<CompletableFuture<Void>> batchFutures = getFuturesStream(idSubLists)
  3. .map(future -> future.thenApply(this::listByNormalize))
  4. .map(future -> future.thenAccept(modelService::batchUpdateData))
  5. .collect(Collectors.toList());
  6. List<Void> results = batchFutures.stream()
  7. .map(CompletableFuture::join)
  8. .collect(Collectors.toList());
  9. }

调整线程池的大小

《Java并发编程实战》一书中,Brian Goetz和合著者们为线程池大小的优化提供了不少中肯的建议。这非常重要,如果线程池中线程的数量过多,最终它们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上。反之,如果线程的数目过少,正如你的应用所面临的情况,处理器的一些核可能就无法充分利用。Brian Goetz建议,线程池大小与处理器的利用率之比可以使用下面的公式进行估算:

$$N_{threads} = N_{CPU} * U_{CPU} * (1 + \frac{W}{C})$$

其中:

  • $N_{CPU}$是处理器的核的数目,可以通过Runtime.getRuntime().availableProcessors()得到
  • $U_{CPU}$是期望的CPU利用率(该值应该介于0和1之间)
  • $\frac{W}{C}$是等待时间与计算时间的比率,比如说IO操作即为等待时间,计算处理即为计算时间

并行——使用流还是CompletableFutures?

对集合进行并行计算有两种方式:要么将其转化为并行流,利用map这样的操作开展工作,要么枚举出集合中的每一个元素,创建新的线程,在CompletableFuture内对其进行操作。后者提供了更多的灵活性,可以调整线程池的大小,而这能帮助确保整体的计算不会因为线程都在等待I/O而发生阻塞。

使用这些API的建议如下:

  • 如果进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
  • 反之,如果并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好,可以依据等待/计算,或者$\frac{W}{C}$的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性很难判断到底什么时候触发了等待。

日期和时间API

使用指南:https://www.yuque.com/docs/share/ee5ef8a7-d261-4593-bd08-2a7a7d2c11ca?#(密码:gtag) 《时区工具类使用指南》

项目地址

GitHub:java8-fluent

参考

分享并记录所学所见

怎样用Java 8优雅的开发业务的更多相关文章

  1. 我们一起来排序——使用Java语言优雅地实现常用排序算法

    破阵子·春景 燕子来时新社,梨花落后清明. 池上碧苔三四点,叶底黄鹂一两声.日长飞絮轻. 巧笑同桌伙伴,上学径里逢迎. 疑怪昨宵春梦好,元是今朝Offer拿.笑从双脸生. 排序算法--最基础的算法,互 ...

  2. 你的Kubernetes Java应用优雅停机了吗?

    Java 应用优雅停机 我们首先考虑下,一般在什么场景下数据会丢失呢? 升级服务时 pod重启时 服务器断电时 因为服务器断电属于极端情况,我们暂且不考虑.那就只有 Java 退出时我们要保证数据的完 ...

  3. 哦,这就是java的优雅停机?(实现及原理)

    优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊! 其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程.释放连接资源等. 再比如,就是不会让调用方的 ...

  4. java如何优雅的实现时间控制

    前言:最近小王同学又遇到了一个需求:线上的业务运行了一段时间,后来随着使用人数增多,出现了一个问题是这样的,一个订单会重复创建几次,导致数据库里出现了很多垃圾数据.在测试同学的不断测试下,发现问题出在 ...

  5. 编码规范 | Java函数优雅之道(上)

    导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化代码,提高代码的质量,是提升系统生命力的有效手段之一.软件系统思维有句话“Less coding, ...

  6. 编码规范 | Java函数优雅之道(下)

    上文背景 本文总结了一套与Java函数相关的编码规则,旨在给广大Java程序员一些编码建议,有助于大家编写出更优雅.更高质.更高效的代码. 内部函数参数尽量使用基础类型 案例一:内部函数参数尽量使用基 ...

  7. Java函数优雅之道

    https://www.cnblogs.com/amap_tech/p/11320171.html 导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化 ...

  8. Java 函数优雅之道

    导读 随着软件项目代码的日积月累,系统维护成本变得越来越高,是所有软件团队面临的共同问题.持续地优化代码,提高代码的质量,是提升系统生命力的有效手段之一.软件系统思维有句话“Less coding, ...

  9. Java如何优雅地使用close()?

    注:本文出自博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 本文源链接:https://www.cnblogs.com/chloneda/p/java-clo ...

随机推荐

  1. 【模版】【P3806】点分治

    (7.17)早就想学点分治了--今天状态不太在线,眯一会写篇笔记来理理思路. ------------------------------------------------------------- ...

  2. css实现元素环形旋转

    元素中心旋转效果记录 先上代码 //css代码 .header{   -webkit-animation:rotateImg 1s linear infinite;   /*rotateImg对应下方 ...

  3. Mysql命令、常用函数

    一.sql命令行 查看数据库 show database : 选择使用的数据库 use 数据库名  : 查看表 show tables ; 查询表 select * from 表名     高版本my ...

  4. 杂篇-之裸眼3D渲染相机及画面矫正

    主题公园类,或大型广场led屏幕,直角幕,三维影片如何制作,和最终画面如何矫正还原. 前两天,一朋友做的项目,大概就是一个柱状的led屏幕,可能是立在广场街角等处,如果这样子,那个柱状体的有颜色的两面 ...

  5. ASP.NET Core管道详解[6]: ASP.NET Core应用是如何启动的?[下篇]

    要承载一个ASP.NET Core应用,只需要将GenericWebHostService服务注册到承载系统中即可.但GenericWebHostService服务具有针对其他一系列服务的依赖,所以在 ...

  6. PHP 的$server变量

    PHP中$_SERVER["QUERY_STRING"]函数   详解PHP中$_SERVER函数的QUERY_STRING和 $_SERVER存储当前服务器信息,其中有几个值 如 ...

  7. jarvisoj flag在管理员手上

    jarvisoj flag在管理员手上 涉及知识点: (1)代码审计和cookie注入 (2)哈希长度拓展攻击 解析: 进入题目的界面.看到 那么就是想方设法的变成admin了.挂上御剑开始审计.发现 ...

  8. [GXYCTF2019] MISC杂项题

    buuoj复现 1,佛系青年 下载了之后是一个加密的txt文件和一张图片 分析图片无果,很讨厌这种脑洞题,MISC应该给一点正常的线索加部分脑洞而不是出干扰信息来故意让选手走错方向,当时比赛做这道题的 ...

  9. PR全套插件一键安装

    PR全套插件一键安装,无需注册码软件也是我在别的地方搬来的,自己用着很好,决定分享出来! 我的PR版本是2019,用着没有任何问题.我没有安装其他版本PR,所以无法测试,不过应该是可以用的. 使用截图 ...

  10. sudo rm -rf /*含义

    sudo -----  管理员权限 rm ------ remove 移除 rf ------ recursive递归  force强制 /* ------ 目录下所有文档