どのようにHashMap一行のコードで単語の出現回数を統計しますか?


前言
JDKは常に繰り返し更新されており、多くの私たちが熟知しているクラスもこっそりといくつかの新しい方法特性を追加しています。例えば私たちが一番よく使うhashMapです。
今日はHashMapがJDK 8に追加した2つの新しい方法のcomputeとmergeについて説明します。それによってコードの一行が単語統計の機能を実現します。一緒に見てみましょう。
愛はJDK 8の前にあります
JDK 8は、Streamやlamband表現など、多くの非常に有用な新しい特性を紹介してくれました。プログラムをより簡潔にすることができます。
もし私たちが一つの配列の単語の出現回数を統計したいなら、どうすればいいですか?
ここはアルゴリズムではないので、直接にHashMapを使うことができます。

public void countBefore8(){
  Map<String,Integer> wordCount= new HashMap<>();
  String[] wordArray= new String[]{"we","are","the","world","we"};
  for(String word: wordArray){
   //      1,       1
   if(wordCount.containsKey(word)) {
    wordCount.put(word, wordCount.get(word) + 1);
   }else{
    wordCount.put(word, 1);
   }
  }
 }
基本的に流れは上のようです。配列を巡回して、この単語がhashMapにあるかどうかを判断します。もし存在すれば+1です。
ロジックは簡単ですが、ちょっと太って見えます。
心配しないでください。JDK 8があります。
JDK 8ではcomputeを使用しています
まずJDK 8の中のcomputeの定義を見てみます。

default V compute(K key,
   BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
  Objects.requireNonNull(remappingFunction);
  V oldValue = get(key);

  V newValue = remappingFunction.apply(key, oldValue);
  if (newValue == null) {
   // delete mapping
   if (oldValue != null || containsKey(key)) {
    // something to remove
    remove(key);
    return null;
   } else {
    // nothing to do. Leave things as they were.
    return null;
   }
  } else {
   // add or replace old mapping
   put(key, newValue);
   return newValue;
  }
 }
computeには2番目のパラメータBiFunctionがあり、BiFunctionは関数であり、2つのパラメータを入力して、1つのパラメータを返します。
BiFunctionの二つのパラメータはそれぞれkeyとkeyに対応するoldValueである。
私たちの単語の統計を考慮して、直接にoldValue+1でいいです。したがって、computeを使って、方法を書き換えることができます。

public void countAfter8WithCompute(){
  Map<String,Integer> wordCount= new HashMap<>();
  String[] wordArray= new String[]{"we","are","the","world","we"};
  Arrays.asList(wordArray).forEach(word ->{
   wordCount.putIfAbsent(word,0);
   wordCount.compute(word,(w,count)->count+1);
  });
 }
もちろん、私達はputIfAbsentをcomputteに置くことができます。

public void countAfter8WithCompute2(){
  Map<String,Integer> wordCount= new HashMap<>();
  String[] wordArray= new String[]{"we","are","the","world","we"};
  Arrays.asList(wordArray).forEach(word -> wordCount.compute(word,(w, count)->count == null ? 1 : count + 1));
 }
一行のコードが完成しました。
JDK 8ではmergeを使用しています
更にmergeの方法を見てみます。

default V merge(K key, V value,
   BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
  Objects.requireNonNull(remappingFunction);
  Objects.requireNonNull(value);
  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メソッドは3つのパラメータが必要で、最初のパラメータはkeyで、2番目のパラメータはkeyに対応するoldValueは空の値、つまり空のデフォルト値、3番目のパラメータはBiFunctionパラメータです。
違いはBiFunctionの最初のパラメータはoldValueで、二つ目のパラメータはvalueです。
newValueを生成するロジックは、oldValueが存在しない場合はvalueを使用する。oldValueが存在すれば、BiFunctionを呼び出してoldValueとValueを統合します。
私たちは該当コードを下記のように書き出すことができます。

public void countAfter8WithMerge(){
  Map<String,Integer> wordCount= new HashMap<>();
  String[] wordArray= new String[]{"we","are","the","world","we"};
  Arrays.asList(wordArray).forEach(word->wordCount.merge(word, 1, (oldCount, one) -> oldCount + one));
 }
後ろの関数はInteger:sumで代替できます。

public void countAfter8WithMerge(){
  Map<String,Integer> wordCount= new HashMap<>();
  String[] wordArray= new String[]{"we","are","the","world","we"};
  Arrays.asList(wordArray).forEach(word->wordCount.merge(word, 1, Integer::sum));
 }
本明細書の例https://github.com/ddean2009/learn-java-base-9-to-20/tree/master/java-base