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

文章目录

  • 怎样用Java 8优雅的开发业务
    • 函数式编程
      • 流式编程
        • 基本原理
        • 案例
    • 优雅的空处理
    • 新的并发工具类`CompletableFuture`
      • 单机批处理多线程执行模型
        • 模型
        • 案例
      • 调整线程池的大小
      • 并行——使用流还是CompletableFutures?
    • 日期和时间API
    • 项目地址
    • 参考

函数式编程

匿名函数

λ演算

流式编程

基本原理

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

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

案例

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

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

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

  • 通过最小列表收集器获取最小列表
@Test
public void t4() {// 通过最小列表收集器获取最小列表List<BigDecimal> minAttackGrowth = heroes.stream().map(Hero::getAttackGrowth).collect(new MinListCollector<>());System.out.println(minAttackGrowth);List<Hero> minHero = heroes.stream().collect(new MinListCollector<>());System.out.println(minHero);
}
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;import static java.util.stream.Collector.Characteristics.*;/*** 最小列表收集器** @author switch* @since 2020/8/18*/
public class MinListCollector<T extends Comparable<? super T>> implements Collector<T, List<T>, List<T>> {/*** 收集器的特性** @see Characteristics*/private final static Set<Characteristics> CHARACTERISTICS = Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH));private final static int ZERO = 0;/*** 最小值*/private final AtomicReference<T> min = new AtomicReference<>();@Overridepublic Supplier<List<T>> supplier() {// supplier参数用于生成结果容器,容器类型为Areturn ArrayList::new;}@Overridepublic BiConsumer<List<T>, T> accumulator() {// accumulator用于消费元素,也就是归纳元素,这里的T就是元素,它会将流中的元素一个一个与结果容器A发生操作return (list, element) -> {// 获取最小值T minValue = min.get();if (Objects.isNull(minValue)) {// 第一次比较list.add(element);min.set(element);} else if (element.compareTo(minValue) < ZERO) {// 发现更小的值list.clear();list.add(element);min.compareAndSet(minValue, element);} else if (element.compareTo(minValue) == ZERO) {// 与最小值相等list.add(element);}};}@Overridepublic BinaryOperator<List<T>> combiner() {// combiner用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果Areturn (left, right) -> {// 最小值列表合并List<T> leftList = getMinList(left);List<T> rightList = getMinList(right);leftList.addAll(rightList);return leftList;};}private List<T> getMinList(List<T> list) {return list.stream().filter(element -> element.compareTo(min.get()) == ZERO).collect(Collectors.toList());}@Overridepublic Function<List<T>, List<T>> finisher() {// finisher用于将之前整合完的结果R转换成为Areturn Function.identity();}@Overridepublic Set<Characteristics> characteristics() {// characteristics表示当前Collector的特征值,这是个不可变Setreturn CHARACTERISTICS;}
}

优雅的空处理

import org.junit.Test;import java.util.Optional;/*** @author switch* @since 2020/8/18*/
public class OptionalTests {@Testpublic void t1() {// orElseSystem.out.println(Optional.ofNullable(null).orElse("张三"));System.out.println(Optional.ofNullable(null).orElseGet(() -> "李四"));System.out.println(Optional.ofNullable("王五").orElseThrow(NullPointerException::new));}@Testpublic void t2() {// isPresentOptional<String> name = Optional.ofNullable("张三");if (name.isPresent()) {System.out.println(name.get());}}@Testpublic void t3() {// mapOptional<Integer> number = Optional.of("123456").map(Integer::valueOf);if (number.isPresent()) {System.out.println(number.get());}}@Testpublic void t4() {// flatMapOptional<Integer> number = Optional.of("123456").flatMap(s -> Optional.of(Integer.valueOf(s)));if (number.isPresent()) {System.out.println(number.get());}}@Testpublic void t5() {// 过滤String number = "123456";String filterNumber = Optional.of(number).filter(s -> !s.equals(number)).orElse("654321");System.out.println(filterNumber);}
}

新的并发工具类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简化代码。
  • 数据流动
List<List<String>> ->
Stream<List<String>> ->
Stream<List<Model>> ->
Stream<CompletableFuture<List<Model>>> ->
Stream<CompletableFuture<List<映射类型>>> ->
List<CompletableFuture<Void>>
案例
  • ThreadPoolUtil
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;public final class ThreadPoolUtil {public static ThreadPoolTaskExecutor getDefaultExecutor(Integer poolSize, Integer maxPoolSize, Integer queueCapacity) {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setAllowCoreThreadTimeOut(true);executor.setWaitForTasksToCompleteOnShutdown(true);executor.setCorePoolSize(poolSize);executor.setMaxPoolSize(maxPoolSize);executor.setQueueCapacity(queueCapacity);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;}
}
  • ThreadPoolConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;@Configuration
public class ThreadPoolConfig {/*** 计算规则:N(thread) = N(cpu) * U(cpu) * (1 + w/c)* N(thread):线程池大小* N(cpu):处理器核数* U(cpu):期望CPU利用率(该值应该介于0和1之间)* w/c:是等待时间与计算时间的比率,比如说IO操作即为等待时间,计算处理即为计算时间*/private static final Integer TASK_POOL_SIZE = 50;private static final Integer TASK_MAX_POOL_SIZE = 100;private static final Integer TASK_QUEUE_CAPACITY = 1000;@Bean("taskExecutor")public ThreadPoolTaskExecutor taskExecutor() {return ThreadPoolUtil.getDefaultExecutor(TASK_POOL_SIZE, TASK_MAX_POOL_SIZE, TASK_QUEUE_CAPACITY);}
}
  • #getFuturesStream
public Stream<CompletableFuture<List<Model>>> getFuturesStream(List<List<String>> idSubLists) {return idSubLists.stream().map(ids -> CompletableFuture.supplyAsync(() -> modelService.listByIds(ids), taskExecutor));
}
  • #standardisation
public void standardisation() {List<CompletableFuture<Void>> batchFutures = getFuturesStream(idSubLists).map(future -> future.thenApply(this::listByNormalize)).map(future -> future.thenAccept(modelService::batchUpdateData)).collect(Collectors.toList());List<Void> results = batchFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
}

调整线程池的大小

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

其中:

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

并行——使用流还是CompletableFutures?

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

使用这些API的建议如下:

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

日期和时间API

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

项目地址

GitHub:java8-fluent

参考

  • Java 8 实战学习笔记
  • Java 8 函数式编程学习笔记
  • 深入理解Java函数式编程和Streams API

怎样用Java 8优雅的开发业务相关推荐

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

    假如我们从 kafka 拉取数据然后生成任务处理数据,在服务退出时,如何保证内存中的数据能被正常处理完不丢失呢?假如服务是部署在 Kubernetes 中又该如何处理? Java 应用优雅停机 我们首 ...

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

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

  3. Java如何优雅的实现时间控制

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源 | http://rrd.me/gCQHp 前言:需求是这样 ...

  4. ShutdownHook - java中优雅地停止服务

    1.什么是ShutdownHook 在Java程序中可以通过添加关闭钩子,实现在程序退出时关闭资源.平滑退出的功能. 使用Runtime.addShutdownHook(Thread hook)方法, ...

  5. Java 如何优雅的实现时间控制

    前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可 ...

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

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

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

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

  8. java想要生成 字符串,如何在Java中“优雅地”生成String?

    我想生成一个字符串,如sql命令: "INSERT INTO xxx VALUES(XXX, XXX, XXX)" 目前我使用StringBuilder和一些String常量,如& ...

  9. 【Java】优雅停机时的一点思考

    1.概述 转载:http://cxytiandi.com/blog/detail/15386 转载自:徐靖峰 Kirito的技术分享 最近瞥了一眼项目的重启脚本,发现运维一直在使用 kill -9 的 ...

最新文章

  1. mysql保持开启事件调度器_MySQL 5.1.6开始的事件调度器
  2. linux 我的世界 跨平台联机,我的世界跨平台联机 PC、手机等平台数据互通
  3. android中断言_我可以使用断言在Android设备上?
  4. tesseract库
  5. 华南主板超频设置图解_支持XMP2.0,3000稳超3733MHz,威刚龙耀D60G超频体验
  6. c语言中shift f12组合建,如何在word中将文本框组合快捷键是什么
  7. DM数据库安装过程--基于NeoKylin操作系统详解
  8. AD19——双面PCB快速铺铜
  9. 宁夏开票系统服务器地址,宁夏增值税发票综合服务平台网址
  10. 好玩的手机淘宝社群,发单初体验
  11. 树莓派获取LAN ip地址并发送到微信
  12. QT的Frame背景图片设置自学版
  13. 强化学习教程(四):从PDG到DDPG的原理及tf代码实现详解
  14. Vue+Axios+ElementUI 远程搜索问题(返回promise对象问题)
  15. java+selenium的入门 案例 selenium包 谷歌驱动包 火狐驱动包 IE驱动包 (一)
  16. 我的Winsxs目录清理脚本
  17. 魔术方法2-上下文管理
  18. 获取手机串码——手机唯一标示
  19. spring mvc的DataBinder、Validator、BeanWrapper、ConversionService、Formatter
  20. 通用万能excel导入的Python实现

热门文章

  1. rpm | 升级软件包
  2. 阿里云ECS服务器部署HADOOP集群(三):ZooKeeper 完全分布式集群搭建
  3. Tensorflow MNIST 手写识别
  4. PHP通知弹窗代码_公告弹窗
  5. windows上的一些命令和工具
  6. 前端使用js发起http请求的几种方法
  7. 排列公式和组合公式_排列与组合:排列公式与组合公式之间有什么区别?
  8. 增删改查通用测试用例-禅道模板
  9. mysql 与oracle 批量插入的 sql 总结
  10. Matlab2018a安装成功后,打开出现licensing error:-8523