為引入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表達式相關。
相比Collection
,Map
中加入了更多的方法,下面一起了解一下。
(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)
方法,只有在當前Map
中key
正好映射到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)
,只有在當前Map
中key
的映射存在時才用value
去替換原來的值,否則什么也不做。replace(K key, V oldValue, V newValue)
,只有在當前Map
中key
的映射存在且等於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)
,作用是:如果Map
中key
對應的映射不存在或者為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