原創:扣釘日記(微信公衆号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似乎開竅了