前言
我們先從最熟悉的 Java集合框架(Java Collections Framework, JCF) 開始說起。
為引入Lambda表達(dá)式,Java8新增了 java.util.funcion 包味悄,里面包含常用的?函數(shù)接口?除嘹,這是Lambda表達(dá)式的基礎(chǔ)系忙,Java集合框架也新增部分接口,以便與Lambda表達(dá)式對(duì)接蓄拣。
首先回顧一下Java集合框架的接口繼承結(jié)構(gòu):
上圖中綠色標(biāo)注的接口類,表示在Java8中加入了新的接口方法努隙,當(dāng)然由于繼承關(guān)系球恤,他們相應(yīng)的子類也都會(huì)繼承這些新方法。下表詳細(xì)列舉了這些方法剃法。
這些新加入的方法大部分要用到 java.util.function 包下的接口碎捺,這意味著這些方法大部分都跟Lambda表達(dá)式相關(guān)。我們將逐一學(xué)習(xí)這些方法贷洲。
Collection中的新方法
如上所示收厨,接口 Collection 和 List 新加入了一些方法,我們以是 List 的子類 ArrayList 為例來說明优构。了解 Java7 ArrayList 實(shí)現(xiàn)原理 诵叁,將有助于理解下文。
forEach()
該方法的簽名為 void forEach(Consumer<? super E> action) 钦椭,作用是對(duì)容器中的每個(gè)元素執(zhí)行 action 指定的動(dòng)作拧额,其中 Consumer 是個(gè)函數(shù)接口碑诉,里面只有一個(gè)待實(shí)現(xiàn)方法 void accept(T t) (后面我們會(huì)看到,這個(gè)方法叫什么根本不重要侥锦,你甚至不需要記憶它的名字)进栽。
需求: 假設(shè)有一個(gè)字符串列表,需要打印出其中所有長度大于3的字符串.
Java7及以前我們可以用增強(qiáng)的for循環(huán)實(shí)現(xiàn):
// 使用曾強(qiáng)for循環(huán)迭代
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(String str : list){
if(str.length()>3)
System.out.println(str);
}
現(xiàn)在使用 forEach() 方法結(jié)合匿名內(nèi)部類恭垦,可以這樣實(shí)現(xiàn):
// 使用forEach()結(jié)合匿名內(nèi)部類迭代
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer(){
@Override
public void accept(String str){
if(str.length()>3)
System.out.println(str);
}
});
上述代碼調(diào)用 forEach() 方法快毛,并使用匿名內(nèi)部類實(shí)現(xiàn) Comsumer 接口。到目前為止我們沒看到這種設(shè)計(jì)有什么好處番挺,但是不要忘記Lambda表達(dá)式唠帝,使用Lambda表達(dá)式實(shí)現(xiàn)如下:
// 使用forEach()結(jié)合Lambda表達(dá)式迭代
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach( str -> {
if(str.length()>3)
System.out.println(str);
});
上述代碼給 forEach() 方法傳入一個(gè)Lambda表達(dá)式,我們不需要知道 accept() 方法玄柏,也不需要知道 Consumer 接口襟衰,類型推導(dǎo)幫我們做了一切。
removeIf()
該方法簽名為 boolean removeIf(Predicate filter) 粪摘,作用是?刪除容器中所有滿足 filter 指定條件的元素?瀑晒,其中 Predicate 是一個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)方法 boolean test(T t) 赶熟,同樣的這個(gè)方法的名字根本不重要瑰妄,因?yàn)橛玫臅r(shí)候不需要書寫這個(gè)名字。
需求: 假設(shè)有一個(gè)字符串列表映砖,需要?jiǎng)h除其中所有長度大于3的字符串间坐。
我們知道如果需要在迭代過程沖對(duì)容器進(jìn)行刪除操作必須使用迭代器,否則會(huì)拋出 ConcurrentModificationException 邑退,所以上述任務(wù)傳統(tǒng)的寫法是:
// 使用迭代器刪除列表元素
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Iterator it = list.iterator();
while(it.hasNext()){
if(it.next().length()>3) // 刪除長度大于3的元素
it.remove();
}
現(xiàn)在使用 removeIf() 方法結(jié)合匿名內(nèi)部類竹宋,我們可是這樣實(shí)現(xiàn):
// 使用removeIf()結(jié)合匿名名內(nèi)部類實(shí)現(xiàn)
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate(){ // 刪除長度大于3的元素
@Override
public boolean test(String str){
return str.length()>3;
}
});
上述代碼使用 removeIf() 方法,并使用匿名內(nèi)部類實(shí)現(xiàn) Precicate 接口地技。相信你已經(jīng)想到用Lambda表達(dá)式該怎么寫了:
// 使用removeIf()結(jié)合Lambda表達(dá)式實(shí)現(xiàn)
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(str -> str.length()>3); // 刪除長度大于3的元素
使用Lambda表達(dá)式不需要記憶 Predicate 接口名蜈七,也不需要記憶 test() 方法名,只需要知道此處需要一個(gè)返回布爾類型的Lambda表達(dá)式就行了莫矗。
replaceAll()
該方法簽名為 void replaceAll(UnaryOperator<E> operator) 飒硅,作用是 對(duì)每個(gè)元素執(zhí)行 operator 指定的操作,并用操作結(jié)果來替換原來的元素 作谚。其中 UnaryOperator 是一個(gè)函數(shù)接口三娩,里面只有一個(gè)待實(shí)現(xiàn)函數(shù) T apply(T t) 。
需求: 假設(shè)有一個(gè)字符串列表妹懒,將其中所有長度大于3的元素轉(zhuǎn)換成大寫雀监,其余元素不變。
Java7及之前似乎沒有優(yōu)雅的辦法:
// 使用下標(biāo)實(shí)現(xiàn)元素替換
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(int i=0; i
String str = list.get(i);
if(str.length()>3)
list.set(i, str.toUpperCase());
}
使用 replaceAll() 方法結(jié)合匿名內(nèi)部類可以實(shí)現(xiàn)如下:
// 使用匿名內(nèi)部類實(shí)現(xiàn)
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(new UnaryOperator(){
@Override
public String apply(String str){
if(str.length()>3)
return str.toUpperCase();
return str;
}
});
上述代碼調(diào)用 replaceAll() 方法,并使用匿名內(nèi)部類實(shí)現(xiàn) UnaryOperator 接口会前。我們知道可以用更為簡(jiǎn)潔的Lambda表達(dá)式實(shí)現(xiàn):
// 使用Lambda表達(dá)式實(shí)現(xiàn)
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(str -> {
if(str.length()>3)
return str.toUpperCase();
return str;
});
sort()
該方法定義在 List 接口中好乐,方法簽名為 void sort(Comparator c) ,該方法?根據(jù) c 指定的比較規(guī)則對(duì)容器元素進(jìn)行排序?瓦宜。 Comparator 接口我們并不陌生蔚万,其中有一個(gè)方法 int compare(T o1, T o2) 需要實(shí)現(xiàn),顯然該接口是個(gè)函數(shù)接口临庇。
需求: 假設(shè)有一個(gè)字符串列表笛坦,按照字符串長度增序?qū)υ嘏判颉?/p>
由于Java7以及之前 sort() 方法在 Collections 工具類中,所以代碼要這樣寫:
// Collections.sort()方法
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Collections.sort(list, new Comparator(){
@Override
public int compare(String str1, String str2){
return str1.length()-str2.length();
}
});
現(xiàn)在可以直接使用 List.sort()方法 苔巨,結(jié)合Lambda表達(dá)式,可以這樣寫:
// List.sort()方法結(jié)合Lambda表達(dá)式
ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length()-str2.length());
spliterator()
方法簽名為 Spliterator spliterator() 废离,該方法返回容器的?可拆分迭代器?侄泽。從名字來看該方法跟 iterator() 方法有點(diǎn)像,我們知道 Iterator 是用來迭代容器的蜻韭, Spliterator 也有類似作用悼尾,但二者有如下不同:
Spliterator 既可以像 Iterator 那樣逐個(gè)迭代,也可以批量迭代肖方。批量迭代可以降低迭代的開銷闺魏。
Spliterator 是可拆分的,一個(gè) Spliterator 可以通過調(diào)用 Spliterator<T> trySplit() 方法來嘗試分成兩個(gè)俯画。一個(gè)是 this 析桥,另一個(gè)是新返回的那個(gè),這兩個(gè)迭代器代表的元素沒有重疊艰垂。
可通過(多次)調(diào)用 Spliterator.trySplit() 方法來分解負(fù)載泡仗,以便多線程處理。
stream()和parallelStream()
stream() 和 parallelStream() 分別?返回該容器的 Stream 視圖表示?猜憎,不同之處在于 parallelStream() 返回并行的 Stream 娩怎。?Stream 是Java函數(shù)式編程的核心類?,我們會(huì)在后面章節(jié)中學(xué)習(xí)胰柑。
Map中的新方法
相比 Collection 截亦, Map 中加入了更多的方法,我們以 HashMap 為例來逐一探秘柬讨。了解 Java7 HashMap 實(shí)現(xiàn)原理 崩瓤,將有助于理解下文。
forEach()
該方法簽名為 void forEach(BiConsumer action) 姐浮,作用是?對(duì) Map 中的每個(gè)映射執(zhí)行 action 指定的操作?谷遂,其中 BiConsumer 是一個(gè)函數(shù)接口,里面有一個(gè)待實(shí)現(xiàn)方法 void accept(T t, U u) 卖鲤。 BinConsumer 接口名字和 accept() 方法名字都不重要肾扰,請(qǐng)不要記憶他們畴嘶。
需求: 假設(shè)有一個(gè)數(shù)字到對(duì)應(yīng)英文單詞的Map风题,請(qǐng)輸出Map中的所有映射關(guān)系.
Java7以及之前經(jīng)典的代碼如下:
// Java7以及之前迭代Map
HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry entry : map.entrySet()){
System.out.println(entry.getKey() + "=" + entry.getValue());
}
使用 Map.forEach() 方法芭析,結(jié)合匿名內(nèi)部類,代碼如下:
// 使用forEach()結(jié)合匿名內(nèi)部類迭代Map
HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach(new BiConsumer(){
@Override
public void accept(Integer k, String v){
System.out.println(k + "=" + v);
}
});
上述代碼調(diào)用 forEach() 方法梦皮,并使用匿名內(nèi)部類實(shí)現(xiàn) BiConsumer 接口偷拔。當(dāng)然蒋院,實(shí)際場(chǎng)景中沒人使用匿名內(nèi)部類寫法,因?yàn)橛蠰ambda表達(dá)式:
// 使用forEach()結(jié)合Lambda表達(dá)式迭代Map
HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach((k, v) -> System.out.println(k + "=" + v));
}
getOrDefault()
該方法跟Lambda表達(dá)式?jīng)]關(guān)系莲绰,但是很有用欺旧。方法簽名為 V getOrDefault(Object key, V defaultValue) ,作用是 按照給定的 key 查詢 Map 中對(duì)應(yīng)的 value 蛤签,如果沒有找到則返回 defaultValue 辞友。使用該方法程序員可以省去查詢指定鍵值是否存在的麻煩.
需求; 假設(shè)有一個(gè)數(shù)字到對(duì)應(yīng)英文單詞的Map震肮,輸出4對(duì)應(yīng)的英文單詞称龙,如果不存在則輸出NoValue
// 查詢Map中指定的值,不存在時(shí)使用默認(rèn)值
HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
// Java7以及之前做法
if(map.containsKey(4)){ // 1
System.out.println(map.get(4));
}else{
System.out.println("NoValue");
}
// Java8使用Map.getOrDefault()
System.out.println(map.getOrDefault(4, "NoValue")); // 2
putIfAbsent()
該方法跟Lambda表達(dá)式?jīng)]關(guān)系戳晌,但是很有用鲫尊。方法簽名為 V putIfAbsent(K key, V value) ,作用是只有在?不存在 key 值的映射或映射值為 null 時(shí)?沦偎,才將 value 指定的值放入到 Map 中疫向,否則不對(duì) Map 做更改.該方法將條件判斷和賦值合二為一,使用起來更加方便.
remove()
我們都知道 Map 中有一個(gè) remove(Object key) 方法扛施,來根據(jù)指定 key 值刪除 Map 中的映射關(guān)系鸿捧;Java8新增了 remove(Object key, Object value) 方法,只有在當(dāng)前 Map 中?key 正好映射到 value 時(shí)?才刪除該映射疙渣,否則什么也不做.
replace()
在Java7及以前匙奴,要想替換 Map 中的映射關(guān)系可通過 put(K key, V value) 方法實(shí)現(xiàn),該方法總是會(huì)用新值替換原來的值.為了更精確的控制替換行為妄荔,Java8在 Map 中加入了兩個(gè) replace() 方法泼菌,分別如下:
replace(K key, V value) ,只有在當(dāng)前 Map 中?key 的映射存在時(shí)?才用 value 去替換原來的值啦租,否則什么也不做.
replace(K key, V oldValue, V newValue) 哗伯,只有在當(dāng)前 Map 中?key 的映射存在且等于 oldValue 時(shí)?才用 newValue 去替換原來的值,否則什么也不做.
replaceAll()
該方法簽名為 replaceAll(BiFunction<? super K,? super V,? extends V> function) 篷角,作用是對(duì) Map 中的每個(gè)映射執(zhí)行 function 指定的操作焊刹,并用 function 的執(zhí)行結(jié)果替換原來的 value ,其中 BiFunction 是一個(gè)函數(shù)接口,里面有一個(gè)待實(shí)現(xiàn)方法 R apply(T t, U u) .不要被如此多的函數(shù)接口嚇到虐块,因?yàn)槭褂玫臅r(shí)候根本不需要知道他們的名字.
需求: 假設(shè)有一個(gè)數(shù)字到對(duì)應(yīng)英文單詞的Map俩滥,請(qǐng)將原來映射關(guān)系中的單詞都轉(zhuǎn)換成大寫.
Java7以及之前經(jīng)典的代碼如下:
// Java7以及之前替換所有Map中所有映射關(guān)系
HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry entry : map.entrySet()){
entry.setValue(entry.getValue().toUpperCase());
}
使用 replaceAll() 方法結(jié)合匿名內(nèi)部類,實(shí)現(xiàn)如下:
// 使用replaceAll()結(jié)合匿名內(nèi)部類實(shí)現(xiàn)
HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll(new BiFunction(){
@Override
public String apply(Integer k, String v){
return v.toUpperCase();
}
});
上述代碼調(diào)用 replaceAll() 方法贺奠,并使用匿名內(nèi)部類實(shí)現(xiàn) BiFunction 接口霜旧。更進(jìn)一步的,使用Lambda表達(dá)式實(shí)現(xiàn)如下:
// 使用replaceAll()結(jié)合Lambda表達(dá)式實(shí)現(xiàn)
HashMap map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll((k, v) -> v.toUpperCase());
簡(jiǎn)潔到讓人難以置信.
merge()
該方法簽名為 merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 儡率,作用是:
如果 Map 中 key 對(duì)應(yīng)的映射不存在或者為 null 挂据,則將 value (不能是 null )關(guān)聯(lián)到 key 上;
否則執(zhí)行 remappingFunction 儿普,如果執(zhí)行結(jié)果非 null 則用該結(jié)果跟 key 關(guān)聯(lián)崎逃,否則在 Map 中刪除 key 的映射.
參數(shù)中 BiFunction 函數(shù)接口前面已經(jīng)介紹過,里面有一個(gè)待實(shí)現(xiàn)方法 R apply(T t, U u) .
merge() 方法雖然語義有些復(fù)雜眉孩,但該方法的用方式很明確婚脱,一個(gè)比較常見的場(chǎng)景是將新的錯(cuò)誤信息拼接到原來的信息上,比如:
map.merge(key, newMsg, (v1, v2) -> v1+v2);
compute()
該方法簽名為 compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 勺像,作用是把 remappingFunction 的計(jì)算結(jié)果關(guān)聯(lián)到 key 上,如果計(jì)算結(jié)果為 null 错森,則在 Map 中刪除 key 的映射.
要實(shí)現(xiàn)上述 merge() 方法中錯(cuò)誤信息拼接的例子吟宦,使用 compute() 代碼如下:
map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));
computeIfAbsent()
該方法簽名為 V computeIfAbsent(K key, Function mappingFunction) ,作用是:只有在當(dāng)前 Map 中?不存在 key 值的映射或映射值為 null 時(shí)涩维,才調(diào)用 mappingFunction 殃姓,并在 mappingFunction 執(zhí)行結(jié)果非 null 時(shí),將結(jié)果跟 key 關(guān)聯(lián).
Function 是一個(gè)函數(shù)接口瓦阐,里面有一個(gè)待實(shí)現(xiàn)方法 R apply(T t) .
computeIfAbsent() 常用來對(duì) Map 的某個(gè) key 值建立初始化映射.比如我們要實(shí)現(xiàn)一個(gè)多值映射蜗侈, Map 的定義可能是 Map<K,Set<V>> ,要向 Map 中放入新值睡蟋,可通過如下代碼實(shí)現(xiàn):
Map> map = new HashMap<>();
// Java7及以前的實(shí)現(xiàn)方式
if(map.containsKey(1)){
map.get(1).add("one");
}else{
Set valueSet = new HashSet();
valueSet.add("one");
map.put(1, valueSet);
}
// Java8的實(shí)現(xiàn)方式
map.computeIfAbsent(1, v -> new HashSet()).add("yi");
使用 computeIfAbsent() 將條件判斷和添加操作合二為一踏幻,使代碼更加簡(jiǎn)潔.
computeIfPresent()
該方法簽名為 V computeIfPresent(K key, BiFunction remappingFunction) ,作用跟 computeIfAbsent() 相反戳杀,即该面,只有在當(dāng)前 Map 中?存在 key 值的映射且非 null 時(shí)?,才調(diào)用 remappingFunction 信卡,如果 remappingFunction 執(zhí)行結(jié)果為 null 隔缀,則刪除 key 的映射,否則使用該結(jié)果替換 key 原來的映射.
這個(gè)函數(shù)的功能跟如下代碼是等效的:
// Java7及以前跟computeIfPresent()等效的代碼
if (map.get(key) != null) {
V oldValue = map.get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null)
map.put(key, newValue);
else
map.remove(key);
return newValue;
}
return null;
總結(jié)
Java8為容器新增一些有用的方法傍菇,這些方法有些是為?完善原有功能?猾瘸,有些是為?引入函數(shù)式編程?,學(xué)習(xí)和使用這些方法有助于我們寫出更加簡(jiǎn)潔有效的代碼.
函數(shù)接口?雖然很多,但絕大多數(shù)時(shí)候我們根本不需要知道它們的名字牵触,書寫Lambda表達(dá)式時(shí)類型推斷幫我們做了一切.
如果你是一名程序員淮悼,如果你剛好又是Java程序員,恰巧剛好你的技術(shù)又遇到了瓶頸但是你又拒絕平庸荒吏,期待蛻變敛惊,想進(jìn)入一線互聯(lián)網(wǎng)公司或者給自己漲薪
我這里剛好有一套自己保存的Java進(jìn)階學(xué)習(xí)資料。包含了Spring框架绰更、Mybatis框架SpringBoot框架瞧挤、SpringMVC框架、SpringCloud微服務(wù)儡湾、Dubbo框架特恬、Redis緩存、RabbitMq消息徐钠、JVM調(diào)優(yōu)癌刽、Tomcat容器、MySQL數(shù)據(jù)庫
之前的兩千人群滿了 這個(gè)是新群Java高級(jí)進(jìn)階群:963,944.895尝丐,免費(fèi)發(fā)送的喲