lambda表達式操作map


為引入Lambda表達式,Java8新增了java.util.funcion包,里面包含常用的函數接口,這是Lambda表達式的基礎,Java集合框架也新增部分接口,以便與Lambda表達式對接。

首先回顧一下Java集合框架的接口繼承結構:

上圖中綠色標注的接口類,表示在Java8中加入了新的接口方法,當然由於繼承關系,他們相應的子類也都會繼承這些新方法。下表詳細列舉了這些方法:

接口名 Java8新加入的方法
Collection removeIf()、spliterator()、 stream()、 parallelStream() 、forEach()
List replaceAll() 、sort()
Map getOrDefault()、 forEach()、 replaceAll()、 putIfAbsent() 、remove()、 replace() 、computeIfAbsent() 、computeIfPresent()、 compute()、 merge()

這些新加入的方法大部分要用到java.util.function包下的接口,這意味着這些方法大部分都跟Lambda表達式相關。


相比CollectionMap中加入了更多的方法,下面一起了解一下。

(1)forEach() 以hashMap為例說明forEach()方法

該方法簽名為void forEach(BiConsumer<? super K,? super V> action)作用是對Map中的每個映射執行action指定的操作,其中BiConsumer是一個函數接口,里面有一個待實現方法void accept(T t, U u)BinConsumer接口名字和accept()方法名字都不重要,請不要記憶他們。

需求:假設有一個數字到對應英文單詞的Map,請輸出Map中的所有映射關系。

Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");

// Java7以及之前寫法
for (Map.Entry<Integer, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

// 使用Map.forEach()方法,並使用匿名內部類實現BiConsumer接口
map.forEach(new BiConsumer<Integer, String>() {
    @Override
    public void accept(Integer integer, String s) {
        System.out.println("key=" + integer + " value=" + s);
    }
});

// 使用lambda表達式
map.forEach((k,v)-> System.out.println("key="+k+" value="+v));

(2)getOrDefault() 以hashMap為例說明getOrDefault()方法

該方法跟Lambda表達式沒關系,但是很有用。方法簽名為V getOrDefault(Object key, V defaultValue)作用是按照給定的key查詢Map中對應的value,如果沒有找到則返回設置的默認值defaultValue使用該方法程序員可以省去查詢指定鍵值是否存在的麻煩。

需求;假設有一個數字到對應英文單詞的Map,輸出4對應的英文單詞,如果不存在則輸出NoValue。

Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");

// Java7以及之前做法
if (map.containsKey(6)) {
    System.out.println(map.get(4));
} else {
    System.out.println("NoValue");
}

// Java8使用Map.getOrDefault(),如果不存在直接返回NoValue
System.out.println(map.getOrDefault(6, "NoValue"));

(3)putIfAbsent() 以hashMap為例說明putIfAbsent()方法

該方法跟Lambda表達式沒關系,但是很有用。方法簽名為V putIfAbsent(K key, V value)作用是只有在不存在key值的映射或映射值為null時,才將value指定的值放入到Map中,否則不對Map做更改。該方法將條件判斷和賦值合二為一,使用起來更加方便。

Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");
map.put(6,null);

map.putIfAbsent(6, "null-six");
map.forEach((k, v) -> {
    System.out.println("k=" + k + " v=" + v);
});

(4)remove(Object key, Object value) 以hashMap為例說明remove()方法

我們都知道Map中有一個remove(Object key)方法,來根據指定key值刪除Map中的映射關系;Java8新增了remove(Object key, Object value)方法,只有在當前Mapkey正好映射到value時才刪除該映射,否則什么也不做。

Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");

map.remove(5,"five");
map.forEach((k,v)-> System.out.println("key="+k+" value="+v));

(5)replace() 以hashMap為例說明replace()方法

在Java7及以前,要想替換Map中的映射關系可通過put(K key, V value)方法實現,該方法總是會用新值替換原來的值。為了更精確的控制替換行為,Java8在Map中加入了兩個replace()方法,分別如下:

  • replace(K key, V value),只有在當前Mapkey的映射存在時才用value去替換原來的值,否則什么也不做。
  • replace(K key, V oldValue, V newValue)只有在當前Mapkey的映射存在且等於oldValue時才用newValue去替換原來的值,否則什么也不做
Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");

// 因為map中key不可重復,因此會替換掉以前key對應的value
map.put(5, "five1");
// 如果存在對應的key,則替換掉對應key的值
map.replace(5, "six");

// map中存在key-value的映射才使用newValue替換掉oldValue
map.replace(5, "five", "newfive");
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

(6)replaceAll() 以hashMap為例說明replaceAll()方法

該方法簽名為replaceAll(BiFunction<? super K,? super V,? extends V> function)作用是對Map中的每個映射執行function指定的操作,並用function的執行結果替換原來的value其中BiFunction是一個函數接口,里面有一個待實現方法R apply(T t, U u)

需求:假設有一個數字到對應英文單詞的Map,請將原來映射關系中的單詞都轉換成大寫。

Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");

// Java7以及之前替換Map中所有映射關系
for (Map.Entry<Integer, String> entry : map.entrySet()) {
    entry.setValue(entry.getValue().toUpperCase());
}
// 遍歷輸出映射key-value
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

// 調用replaceAll()方法,並使用匿名內部類實現BiFunction接口
map.replaceAll(new BiFunction<Integer, String, String>() {
    @Override
    public String apply(Integer integer, String s) {
        return s.toUpperCase();
    }
});
// 遍歷輸出映射key-value
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

// 使用replaceAll()並結合Lambda表達式實現
map.replaceAll((k, v) -> v.toUpperCase());
// 遍歷輸出映射key-value
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

(7)merge() 以hashMap為例說明merge()方法

該方法簽名為merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)作用是:如果Mapkey對應的映射不存在或者為null,則將value(不能是null)關聯到key上,否則執行remappingFunction;如果執行結果非null則用該結果跟key關聯,否則在Map中刪除key的映射。

也就是說如果key存在就把newvalue拼接在這個key對應的value上,如果不存在就把這個newValue與key進行映射,put到map中。如下代碼所示:

Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");
map.put(6, "six");

// 使用匿名內部類實現BiFunction接口
map.merge(6, "+", new BiFunction<String, String, String>() {
    @Override
    public String apply(String s, String s2) {
        return s + s2;
    }
});
// 遍歷輸出key-value
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

// 使用lambda表達式實現
map.merge(6, "+", (s, s2) -> s + s2);
// 遍歷輸出key-value
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

(8)compute() 以hashMap為例說明compute()方法

該方法簽名為compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),作用是把remappingFunction的計算結果關聯到key上,如果計算結果為null,則在Map中刪除key的映射。 也就是說,如果計算的結果為null則把這個key-value映射給刪掉,如果計算結果不為空,則把這個計算結果覆蓋掉以前的value。

Map<Integer, String> map = new HashMap<>(16);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(5, "five");
map.put(6, "six");

// 使用匿名內部類實現BiFunction接口寫法
map.compute(5, new BiFunction<Integer, String, String>() {
  @Override
  public String apply(Integer integer, String s) {
      return s == null ? "null" : s + " is not null";
  }
});

// lambda表達式寫法
map.compute(5, (integer, s) -> s == null ? "null" : s + "is not null");

// 遍歷輸出key-value映射
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

(9)computeIfAbsent() 以hashMap為例說明computeIfAbsent()方法

該方法簽名為V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)作用是:只有在當前Map中不存在key值的映射或映射值為null時,才調用mappingFunction,並在mappingFunction執行結果非null時,將結果跟key關聯。

Function是一個函數接口,里面有一個待實現方法R apply(T t)computeIfAbsent()常用來對Map的某個key值建立初始化映射。

比如我們要實現一個多值映射,Map的定義可能是Map<K,Set<V>>,要向Map中放入新值,可通過如下代碼實現:

// 實現一個key對應多個值
Map<Integer, Set<String>> map = new HashMap<>(16);
// Java7及以前的實現方式
if (map.containsKey(1)) {
    map.get(1).add("one");
} else {
    Set<String> valueSet = new HashSet<>();
    valueSet.add("one");
    map.put(1, valueSet);
}

// Java8的實現方式 即它會判斷一下這個key是否存在並且key對應的value是否為空,
// 如果key存在且key對應的value不為null,則將這個value關聯到對應的key上,即在原來的value中新增一個value
// 如果key不存在,則新增一個key-value映射關系
map.computeIfAbsent(1, v -> new HashSet<>()).add("oneone");

// 遍歷輸出key-value映射
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

使用computeIfAbsent()將條件判斷和添加操作合二為一,使代碼更加簡潔。


(10)computeIfPresent() 以hashMap為例說明computeIfPresent()方法

該方法簽名為V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)作用跟computeIfAbsent()相反,即只有在當前Map中存在key值的映射且非null時,才調用remappingFunction,如果remappingFunction執行結果為null,則刪除key的映射,否則使用該結果替換key原來的映射。

這個函數的功能跟如下代碼是等效的:

Map<Integer, Set<String>> map = new HashMap<>(16);
// Java7及以前的實現方式
if (map.containsKey(1)) {
    map.get(1).add("one");
} else {
    Set<String> valueSet = new HashSet<>();
    valueSet.add("one");
    map.put(1, valueSet);
}

// 匿名內部類實現BiFunction接口,如果key存在並且計算結果不為null時將計算的結果替換掉key對應的原來的值
map.computeIfPresent(8, new BiFunction<Integer, Set<String>, Set<String>>() {
    @Override
    public Set<String> apply(Integer integer, Set<String> strings) {
        Set<String> set = new HashSet<>();
        set.add("888");
        return set;
    }
});

// lambda表達式實現
map.computeIfPresent(8, (integer, strings) -> {
    Set<String> set = new HashSet<>();
    set.add("888");
    return set;
});

// 遍歷輸出key-value映射
map.forEach((k, v) -> System.out.println("key=" + k + " value=" + v));

在使用lambda表達式時需要明白以下兩點:

  • Java8為容器新增一些有用的方法,這些方法有些是為完善原有功能,有些是為引入函數式編程,學習和使用這些方法有助於我們寫出更加簡潔有效的代碼。
  • 函數接口雖然很多,但絕大多數時候我們根本不需要知道它們的名字,書寫Lambda表達式時類型推斷幫我們做了一切。

參考博文:
(1)https://objcoding.com/2019/03/04/lambda/ (非常詳細,值得仔細閱讀)
(2)https://www.runoob.com/java/java8-lambda-expressions.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM