Java8中那些方便又实用的Map函数
原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。
简介
java8之后,常用的Map接口中添加了一些非常实用的函数,可以大大简化一些特定场景的代码编写,提升代码可读性,一起来看看吧。
computeIfAbsent函数
比如,很多时候我们需要对数据进行分组,变成Map<Integer, List<?>>的形式,在java8之前,一般如下实现:
List<Payment> payments = getPayments();
Map<Integer, List<Payment>> paymentByTypeMap = new HashMap<>();
for(Payment payment : payments){
if(!paymentByTypeMap.containsKey(payment.getPayTypeId())){
paymentByTypeMap.put(payment.getPayTypeId(), new ArrayList<>());
}
paymentByTypeMap.get(payment.getPayTypeId())
.add(payment);
}
可以发现仅仅做一个分组操作,代码却需要考虑得比较细致,在Map中无相应值时需要先塞一个空List进去。
但如果使用java8提供的computeIfAbsent方法,代码则会简化很多,如下:
List<Payment> payments = getPayments();
Map<Integer, List<Payment>> paymentByTypeMap = new HashMap<>();
for(Payment payment : payments){
paymentByTypeMap.computeIfAbsent(payment.getPayTypeId(), k -> new ArrayList<>())
.add(payment);
}
computeIfAbsent方法的逻辑是,如果map中没有(Absent)相应的key,则执行lambda表达式生成一个默认值并放入map中并返回,否则返回map中已有的值。
带默认值Map
由于这种需要默认值的Map太常用了,我一般会封装一个工具类出来使用,如下:
public class DefaultHashMap<K, V> extends HashMap<K, V> {
Function<K, V> function;
public DefaultHashMap(Supplier<V> supplier) {
this.function = k -> supplier.get();
}
@Override
@SuppressWarnings("unchecked")
public V get(Object key) {
return super.computeIfAbsent((K) key, this.function);
}
}
然后再这么使用,如下:
List<Payment> payments = getPayments();
Map<Integer, List<Payment>> paymentByTypeMap = new DefaultHashMap<>(ArrayList::new);
for(Payment payment : payments){
paymentByTypeMap.get(payment.getPayTypeId())
.add(payment);
}
呵呵,这玩得有点像python的defaultdict(list)了
临时Cache
有时,在一个for循环中,需要一个临时的Cache在循环中复用查询结果,也可以使用computeIfAbcent,如下:
List<Payment> payments = getPayments();
Map<Integer, PayType> payTypeCacheMap = new HashMap<>();
for(Payment payment : payments){
PayType payType = payTypeCacheMap.computeIfAbsent(payment.getPayTypeId(),
k -> payTypeMapper.queryByPayType(k));
payment.setPayTypeName(payType.getPayTypeName());
}
因为payments中不同payment的pay_type_id极有可能相同,使用此方法可以避免大量重复查询,但如果不用computeIfAbcent函数,代码就有点繁琐晦涩了。
computeIfPresent函数
computeIfPresent函数与computeIfAbcent的逻辑是相反的,如果map中存在(Present)相应的key,则对其value执行lambda表达式生成一个新值并放入map中并返回,否则返回null。
这个函数一般用在两个集合做等值关联的时候,可少写一次判断逻辑,如下:
@Data
public static class OrderPayment {
private Order order;
private List<Payment> payments;
public OrderPayment(Order order) {
this.order = order;
this.payments = new ArrayList<>();
}
public OrderPayment addPayment(Payment payment){
this.payments.add(payment);
return this;
}
}
public static void getOrderWithPayment(){
List<Order> orders = getOrders();
Map<Long, OrderPayment> orderPaymentMap = new HashMap<>();
for(Order order : orders){
orderPaymentMap.put(order.getOrderId(), new OrderPayment(order));
}
List<Payment> payments = getPayments();
//将payment关联到相关的order上
for(Payment payment : payments){
orderPaymentMap.computeIfPresent(payment.getOrderId(),
(k, orderPayment) -> orderPayment.addPayment(payment));
}
}
compute函数
compute函数,其实和computeIfPresent、computeIfAbcent函数是类似的,不过它不关心map中到底有没有值,都执行lambda表达式计算新值并放入map中并返回。
这个函数适合做分组迭代计算,像分组汇总金额的情况,就适合使用compute函数,如下:
List<Payment> payments = getPayments();
Map<Integer, BigDecimal> amountByTypeMap = new HashMap<>();
for(Payment payment : payments){
amountByTypeMap.compute(payment.getPayTypeId(),
(key, oldVal) -> oldVal == null ? payment.getAmount() : oldVal.add(payment.getAmount())
);
}
当oldValue是null,表示map中第一次计算相应key的值,直接给amount就好,而后面再次累积计算时,直接通过add函数汇总就好。
merge函数
可以发现,上面在使用compute汇总金额时,lambda表达式中需要判断是否是第一次计算key值,稍微麻烦了点,而使用merge函数的话,可以进一步简化代码,如下:
List<Payment> payments = getPayments();
Map<Integer, BigDecimal> amountByTypeMap = new HashMap<>();
for(Payment payment : payments){
amountByTypeMap.merge(payment.getPayTypeId(), payment.getAmount(), BigDecimal::add);
}
这个函数太简洁了,merge的第一个参数是key,第二个参数是value,第三个参数是值合并函数。
当是第一次计算相应key的值时,直接放入value到map中,后面再次计算时,使用值合并函数BigDecimal::add计算出新的汇总值,并放入map中即可。
putIfAbsent函数
putIfAbsent从命名上也能知道作用了,当map中没有相应key时才put值到map中,主要用于如下场景:
如将list转换为map时,若list中有重复值时,put与putIfAbsent的区别如下:
- put保留最晚插入的数据。
- putIfAbsent保留最早插入的数据。
forEach函数
说实话,java中要遍历map,写法上是比较啰嗦的,不管是entrySet方式还是keySet方式,如下:
for(Map.Entry<String, BigDecimal> entry: amountByTypeMap.entrySet()){
Integer payTypeId = entry.getKey();
BigDecimal amount = entry.getValue();
System.out.printf("payTypeId: %s, amount: %s \n", payTypeId, amount);
}
再看看在python或go中的写法,如下:
for payTypeId, amount in amountByTypeMap.items():
print("payTypeId: %s, amount: %s \n" % (payTypeId, amount))
可以发现,在python中的map遍历写法要少写好几行代码呢,不过,虽然java在语法层面上并未支持这种写法,但使用map的forEach函数,也可以简化出类似的效果来,如下:
amountByTypeMap.forEach((payTypeId, amount) -> {
System.out.printf("payTypeId: %s, amount: %s \n", payTypeId, amount);
});
总结
一直以来,java因代码编写太繁琐而被开发者们所广泛诟病,但从java8开始,从Map、Stream、var、multiline-string再到record,java在代码编写层面做了大量的简化,java似乎开窍了
Java8中那些方便又实用的Map函数的更多相关文章
- Java8 中使用Stream 让List 转 Map使用总结
在使用 Java 的新特性 Collectors.toMap() 将 List 转换为 Map 时存在一些不容易发现的问题,这里总结一下备查. 空指针风险 java.lang.NullPointerE ...
- 2018.8.15 python 中的sorted()、filter()、map()函数
主要内容: 1.lambda匿名函数 2.sorted() 3.filter() 4.map() 5.递归函数 一. lambda匿名函数 为了解决一些简单的需求而设计的一句话函数 # 计算n的n次方 ...
- [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念
本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...
- jQuery——map()函数以及它的java实现
map()函数小简单介绍 map()函数一直都是我觉得比較有用的函数之中的一个,为什么这么说呢? 先来考虑一下.你是否碰到过下面场景:须要遍历一组对象取出每一个对象的某个属性(比方id)而且用分隔符隔 ...
- java8中map的meger方法的使用
java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...
- java8中的map和reduce
java8中的map和reduce 标签: java8函数式mapreduce 2014-06-19 19:14 10330人阅读 评论(4) 收藏 举报 分类: java(47) FP(2) ...
- 【转】Java8中list转map方法总结
https://blog.csdn.net/zlj1217/article/details/81611834 背景在最近的工作开发之中,慢慢习惯了很多Java8中的Stream的用法,很方便而且也可以 ...
- Java8中list转map
第一种: 取list中某2个字段作为Map的K,V public Map<Long, String> getIdNameMap(List<Account> accounts) ...
- java8 中的时间和数据的变化
java8除了lambda表达式之外还对时间和数组这两块常用API做想应调整, Stream 有几个常用函数: store 排序 (a,b)-> a.compareTo(b) 排出来的结果是正 ...
随机推荐
- PHP实现服务器文件预览
PHP实现服务器里面的所有文件进行预览跟手机文件夹一样 服务器创建一个index.php文件 点我查看 <?php // errors ini_set('display_errors', 1); ...
- 面试现场!月薪3w+的这些数据挖掘SQL面试题你都掌握了吗? ⛵
作者:韩信子@ShowMeAI 数据分析实战系列:https://www.showmeai.tech/tutorials/40 AI 面试题库系列:https://www.showmeai.tech/ ...
- 解决:Error downloading packages: containerd.io-1.6.4-3.1.el7.x86_64: [Errno 256] No more mirrors to try.
问题描述: 今天在安装Docker-ce的时候,安装了半天最后提示下载出错还提示下载速度太慢. 报错如下: 下载软件包时出错:containerd.io-1.6.4-3.1.el7.x86_64:[E ...
- 给定字符串定义char *a = “I love China!”,读入整数n,输出在进行了a = a + n这个赋值操作以后字符指针a对应的字符串
include<stdio.h> include<string.h> int main() { const char *a="I love China!"; ...
- 【设计模式】Java设计模式 - 责任链模式
[设计模式]Java设计模式 - 责任链模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 目录 [设计模式]Java设计模式 - 责 ...
- CentOS7_K8S安装指南
https://www.cnblogs.com/liu-shuai/articles/12177298.html 不能完全按照他来装,因为他装的是15.5的,15.5 有部分组件在阿里云镜像上没有,导 ...
- 跨语言调用C#代码的新方式-DllExport
简介 上一篇文章使用C#编写一个.NET分析器文章发布以后,很多小伙伴都对最新的NativeAOT函数导出比较感兴趣,今天故写一篇短文来介绍一下如何使用它. 在以前,如果有其他语言需要调用C#编写的库 ...
- Linux宝塔如何开启指定的目录浏览功能
哈喽,各位运维晚上好, 今天突发奇想,想给我的个人博客加一个功能,就是如何去打开一个网站文件夹的目录浏览功能,这个还是挺有趣的. 为了以后我还能想起怎么用,我决定记录下来,以便能用,也能给大家一个参考 ...
- Kubernetes中使用ClusterDNS进行服务发现
在k8s集群中,服务是运行在Pod中的,Pod的发现和副本间负载均衡是我们面临的问题.我们使用Service解决了负载均衡的问题,但是集群环境中,service经常伴随着ip的变动而变动,得益于kub ...
- 11. 第十篇 网络组件flanneld安装及使用
文章转载自:https://mp.weixin.qq.com/s?__biz=MzI1MDgwNzQ1MQ==&mid=2247483834&idx=1&sn=b04ec193 ...