首頁  >  文章  >  Java  >  Map.merge()的詳細介紹(附程式碼)

Map.merge()的詳細介紹(附程式碼)

不言
不言轉載
2019-03-22 17:04:095962瀏覽

這篇文章帶給大家的內容是關於Map.merge()的詳細介紹(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

今天介紹Map的merge方法,讓我們來看看它的強大之處。

在JDK的API中,這樣的一個方法它是很特別的,它很新穎,它是值得我們花時間去了解的,同時也推薦你可以運用到實際的專案程式碼中,對你們應該幫助很大。 Map.merge())。這可能是Map中最通用的操作。但它也相當模糊,幾乎很少人會去使用它。

背景介紹

merge() 可以解釋如下:它將新的值賦值給到key中(如果不存在)或更新具有給定值的現有key(UPSERT)。讓我們從最基本的例子開始:計算唯一的單字出現次數。在java8之前的時候,程式碼非常混亂,實際的實現其實已經失去了本質層面的設計意義。

var map = new HashMap<String, Integer>();
words.forEach(word -> {
    var prev = map.get(word);
    if (prev == null) {
        map.put(word, 1);
    } else {
        map.put(word, prev + 1);
    }
});

依照上述程式碼的邏輯,假設給定一個輸入集合,輸出的結果如下;

var words = List.of("Foo", "Bar", "Foo", "Buzz", "Foo", "Buzz", "Fizz", "Fizz");
//...
{Bar=1, Fizz=2, Foo=3, Buzz=2}

改進V1

現在讓我們來重構它,主要去掉它的一些判斷邏輯;

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.put(word, map.get(word) + 1);
});

這樣的改進,是可以滿足我們的重構要求。 putIfAbsent()的具體用法就不過多描述。 putIfAbsent那一行程式碼是一定需要的,否則,後面的邏輯也會報錯。而在下面程式碼中,又出現了put、get這一點會很奇怪,讓我們再繼續的進行改進設計。

改進V2

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.computeIfPresent(word, (w, prev) -> prev + 1);
});

computeIfPresent是僅當 word中的的key存在的時候才呼叫給定的轉換。否則它什麼都不處理。我們透過將key初始化為零來確保key存在,因此增量始終有效。這樣的實現是不是已經夠完美?未必,還有其他的想法可以減少額外的初始化。

words.forEach(word ->
        map.compute(word, (w, prev) -> prev != null ? prev + 1 : 1)
);

compute ()就像是computeIfPresent(),但無論給定key的存在與否如何都會呼叫它。如果key的值不存在,則prev參數為null。將簡單移動if 到隱藏在lambda中的三元表達式也遠遠沒有達到最佳的表現。在我向你展示最終版本之前,讓我們先來看看稍微簡化的預設實作Map.merge()原始碼分析。

改進V3

merge()原始碼
default V merge(K key, V value, BiFunction<V, V, V> remappingFunction) {
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
               remappingFunction.apply(oldValue, value);
    if (newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

程式碼片段勝過千言萬語。閱讀原始碼總是能夠發現新大陸,merge() 適用於兩種情況。如果給定的key不存在,它就變成了put(key, value)。但是,如果key已經存在一些值,我們  remappingFunction 可以選擇合併的方式。這個功能是完美契機上面的場景:

  • 只要傳回新值即可覆寫舊值: (old, new) -> new
  • 只需傳回舊值即可保留舊值: (old, new) -> old
  • #以某種方式合併兩者,例如: (old, new) -> old new
  • 甚至刪除舊值: (old, new) -> null

如你所見,它 merge () 是非常通用的。那麼,我們的問題該如何使用merge()呢?程式碼如下:

words.forEach(word ->
        map.merge(word, 1, (prev, one) -> prev + one)
);

你可以依照以下思路理解:如果沒有key,那麼初始化的value等於1;否則,將1加到現有值。程式碼中的 one 是一個常數,因為我們的場景中,預設一直是加1,具體變化可以隨意切換。

場景

想像一下,merge()真的那麼好用嗎?它的場景可以有什麼?

舉個例子。你有一個帳戶操作類別

class Operation {
    private final String accNo;
    private final BigDecimal amount;
}

以及針對不同帳戶的一系列操作:

operations = List.of(
    new Operation("123", new BigDecimal("10")),
    new Operation("456", new BigDecimal("1200")),
    new Operation("123", new BigDecimal("-4")),
    new Operation("123", new BigDecimal("8")),
    new Operation("456", new BigDecimal("800")),
    new Operation("456", new BigDecimal("-1500")),
    new Operation("123", new BigDecimal("2")),
    new Operation("123", new BigDecimal("-6.5")),
    new Operation("456", new BigDecimal("-600"))
);

我們希望為每個帳戶計算餘額(總營運金額)。假如不用merge(),就變得非常麻煩了:

Map balances = new HashMap<String, BigDecimal>();
operations.forEach(op -> {
    var key = op.getAccNo();
    balances.putIfAbsent(key, BigDecimal.ZERO);
    balances.computeIfPresent(key, (accNo, prev) -> prev.add(op.getAmount()));
});

使用merge之後的程式碼

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), 
                (soFar, amount) -> soFar.add(amount))
);

再進行最佳化的邏輯。

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), BigDecimal::add)
);

當然結果是正確的,這樣簡潔的程式碼心動嗎?對於每個操作,add在給定的amount給定accNo

{ 123 = 9.5,456 = - 100 }

ConcurrentHashMap

當我們再延伸到ConcurrentHashMap來,當 Map.merge的出現,和ConcurrentHashMap的結合那是非常的完美的。這樣的搭配場景是對於那些自動執行插入或更新操作的單執行緒安全的邏輯。

這篇文章到這裡就已經全部結束了,更多其他精彩內容可以關注PHP中文網的Java教學影片專欄!

以上是Map.merge()的詳細介紹(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除