溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

深入淺析Java集合框架與Lambda表達(dá)式

發(fā)布時(shí)間:2020-11-11 16:22:48 來(lái)源:億速云 閱讀:166 作者:Leah 欄目:編程語(yǔ)言

深入淺析Java集合框架與Lambda表達(dá)式?很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

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

首先回顧一下Java集合框架的接口繼承結(jié)構(gòu):

 深入淺析Java集合框架與Lambda表達(dá)式

上圖中綠色標(biāo)注的接口類,表示在Java8中加入了新的接口方法,當(dāng)然由于繼承關(guān)系,他們相應(yīng)的子類也都會(huì)繼承這些新方法。下表詳細(xì)列舉了這些方法。

接口名Java8新加入的方法
CollectionremoveIf() spliterator() stream() parallelStream() forEach()
ListreplaceAll() sort()
MapgetOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()

這些新加入的方法大部分要用到j(luò)ava.util.function包下的接口,這意味著這些方法大部分都跟Lambda表達(dá)式相關(guān)。我們將逐一學(xué)習(xí)這些方法。

Collection中的新方法

如上所示,接口Collection和List新加入了一些方法,我們以是List的子類ArrayList為例來(lái)說(shuō)明。了解Java7ArrayList實(shí)現(xiàn)原理,將有助于理解下文。

forEach()

該方法的簽名為void forEach(Consumer<&#63; super E> action),作用是對(duì)容器中的每個(gè)元素執(zhí)行action指定的動(dòng)作,其中Consumer是個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)方法void accept(T t)(后面我們會(huì)看到,這個(gè)方法叫什么根本不重要,你甚至不需要記憶它的名字)。

需求:假設(shè)有一個(gè)字符串列表,需要打印出其中所有長(zhǎng)度大于3的字符串.

Java7及以前我們可以用增強(qiáng)的for循環(huán)實(shí)現(xiàn):

// 使用曾強(qiáng)for循環(huán)迭代
ArrayList<String> 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<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){
 @Override
 public void accept(String str){
 if(str.length()>3)
  System.out.println(str);
 }
});

上述代碼調(diào)用forEach()方法,并使用匿名內(nèi)部類實(shí)現(xiàn)Comsumer接口。到目前為止我們沒(méi)看到這種設(shè)計(jì)有什么好處,但是不要忘記Lambda表達(dá)式,使用Lambda表達(dá)式實(shí)現(xiàn)如下:

// 使用forEach()結(jié)合Lambda表達(dá)式迭代
ArrayList<String> 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<&#63; super E> filter),作用是刪除容器中所有滿足filter指定條件的元素,其中Predicate是一個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)方法boolean test(T t),同樣的這個(gè)方法的名字根本不重要,因?yàn)橛玫臅r(shí)候不需要書(shū)寫這個(gè)名字。

需求:假設(shè)有一個(gè)字符串列表,需要?jiǎng)h除其中所有長(zhǎng)度大于3的字符串。

我們知道如果需要在迭代過(guò)程沖對(duì)容器進(jìn)行刪除操作必須使用迭代器,否則會(huì)拋出ConcurrentModificationException,所以上述任務(wù)傳統(tǒng)的寫法是:

// 使用迭代器刪除列表元素
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Iterator<String> it = list.iterator();
while(it.hasNext()){
 if(it.next().length()>3) // 刪除長(zhǎng)度大于3的元素
 it.remove();
}

現(xiàn)在使用removeIf()方法結(jié)合匿名內(nèi)部類,我們可是這樣實(shí)現(xiàn):

// 使用removeIf()結(jié)合匿名名內(nèi)部類實(shí)現(xiàn)
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate<String>(){ // 刪除長(zhǎng)度大于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<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(str -> str.length()>3); // 刪除長(zhǎng)度大于3的元素

使用Lambda表達(dá)式不需要記憶Predicate接口名,也不需要記憶test()方法名,只需要知道此處需要一個(gè)返回布爾類型的Lambda表達(dá)式就行了。

replaceAll()

該方法簽名為void replaceAll(UnaryOperator<E> operator),作用是對(duì)每個(gè)元素執(zhí)行operator指定的操作,并用操作結(jié)果來(lái)替換原來(lái)的元素。其中UnaryOperator是一個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)函數(shù)T apply(T t)。

需求:假設(shè)有一個(gè)字符串列表,將其中所有長(zhǎng)度大于3的元素轉(zhuǎn)換成大寫,其余元素不變。

Java7及之前似乎沒(méi)有優(yōu)雅的辦法:

// 使用下標(biāo)實(shí)現(xiàn)元素替換
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(int i=0; i<list.size(); 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<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(new UnaryOperator<String>(){
 @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<String> 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<&#63; super E> c),該方法根據(jù)c指定的比較規(guī)則對(duì)容器元素進(jìn)行排序。Comparator接口我們并不陌生,其中有一個(gè)方法int compare(T o1, T o2)需要實(shí)現(xiàn),顯然該接口是個(gè)函數(shù)接口。

需求:假設(shè)有一個(gè)字符串列表,按照字符串長(zhǎng)度增序?qū)υ嘏判颉?/strong>

由于Java7以及之前sort()方法在Collections工具類中,所以代碼要這樣寫:

// Collections.sort()方法
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Collections.sort(list, new Comparator<String>(){
 @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<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length()-str2.length());

spliterator()

方法簽名為Spliterator<E> spliterator(),該方法返回容器的可拆分迭代器。從名字來(lái)看該方法跟iterator()方法有點(diǎn)像,我們知道Iterator是用來(lái)迭代容器的,Spliterator也有類似作用,但二者有如下不同:

  • Spliterator既可以像Iterator那樣逐個(gè)迭代,也可以批量迭代。批量迭代可以降低迭代的開(kāi)銷。
  • Spliterator是可拆分的,一個(gè)Spliterator可以通過(guò)調(diào)用Spliterator<T> trySplit()方法來(lái)嘗試分成兩個(gè)。一個(gè)是this,另一個(gè)是新返回的那個(gè),這兩個(gè)迭代器代表的元素沒(méi)有重疊。

可通過(guò)(多次)調(diào)用Spliterator.trySplit()方法來(lái)分解負(fù)載,以便多線程處理。

stream()和parallelStream()

stream()和parallelStream()分別返回該容器的Stream視圖表示,不同之處在于parallelStream()返回并行的Stream。Stream是Java函數(shù)式編程的核心類,我們會(huì)在后面章節(jié)中學(xué)習(xí)。

Map中的新方法

相比Collection,Map中加入了更多的方法,我們以HashMap為例來(lái)逐一探秘。了解Java7HashMap實(shí)現(xiàn)原理,將有助于理解下文。

forEach()

該方法簽名為void forEach(BiConsumer<&#63; super K,&#63; super V> 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<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){
 System.out.println(entry.getKey() + "=" + entry.getValue());
}

使用Map.forEach()方法,結(jié)合匿名內(nèi)部類,代碼如下:

// 使用forEach()結(jié)合匿名內(nèi)部類迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach(new BiConsumer<Integer, String>(){
 @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)景中沒(méi)人使用匿名內(nèi)部類寫法,因?yàn)橛蠰ambda表達(dá)式:

// 使用forEach()結(jié)合Lambda表達(dá)式迭代Map
HashMap<Integer, String> 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,如果沒(méi)有找到則返回defaultValue。使用該方法程序員可以省去查詢指定鍵值是否存在的麻煩.

需求;假設(shè)有一個(gè)數(shù)字到對(duì)應(yīng)英文單詞的Map,輸出4對(duì)應(yīng)的英文單詞,如果不存在則輸出NoValue

// 查詢Map中指定的值,不存在時(shí)使用默認(rèn)值
HashMap<Integer, String> 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做更改.該方法將條件判斷和賦值合二為一,使用起來(lái)更加方便.

remove()

我們都知道Map中有一個(gè)remove(Object key)方法,來(lái)根據(jù)指定key值刪除Map中的映射關(guān)系;Java8新增了remove(Object key, Object value)方法,只有在當(dāng)前Map中key正好映射到value時(shí)才刪除該映射,否則什么也不做

replace()

在Java7及以前,要想替換Map中的映射關(guān)系可通過(guò)put(K key, V value)方法實(shí)現(xiàn),該方法總是會(huì)用新值替換原來(lái)的值.為了更精確的控制替換行為,Java8在Map中加入了兩個(gè)replace()方法,分別如下:

  • replace(K key, V value),只有在當(dāng)前Map中key的映射存在時(shí)才用value去替換原來(lái)的值,否則什么也不做.
  • replace(K key, V oldValue, V newValue),只有在當(dāng)前Map中key的映射存在且等于oldValue時(shí)才用newValue去替換原來(lái)的值,否則什么也不做.

replaceAll()

該方法簽名為replaceAll(BiFunction<&#63; super K,&#63; super V,&#63; extends V> function),作用是對(duì)Map中的每個(gè)映射執(zhí)行function指定的操作,并用function的執(zhí)行結(jié)果替換原來(lái)的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)將原來(lái)映射關(guān)系中的單詞都轉(zhuǎn)換成大寫.

Java7以及之前經(jīng)典的代碼如下:

// Java7以及之前替換所有Map中所有映射關(guān)系
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){
 entry.setValue(entry.getValue().toUpperCase());
}

使用replaceAll()方法結(jié)合匿名內(nèi)部類,實(shí)現(xiàn)如下:

// 使用replaceAll()結(jié)合匿名內(nèi)部類實(shí)現(xiàn)
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll(new BiFunction<Integer, String, String>(){
 @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<Integer, String> 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<&#63; super V,&#63; super V,&#63; 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)介紹過(guò),里面有一個(gè)待實(shí)現(xiàn)方法R apply(T t, U u).

merge()方法雖然語(yǔ)義有些復(fù)雜,但該方法的用方式很明確,一個(gè)比較常見(jiàn)的場(chǎng)景是將新的錯(cuò)誤信息拼接到原來(lái)的信息上,比如:

map.merge(key, newMsg, (v1, v2) -> v1+v2);

compute()

該方法簽名為compute(K key, BiFunction<&#63; super K,&#63; super V,&#63; 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 &#63; newMsg : v.concat(newMsg));

computeIfAbsent()

該方法簽名為V computeIfAbsent(K key, Function<&#63; super K,&#63; extends V> 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()常用來(lái)對(duì)Map的某個(gè)key值建立初始化映射.比如我們要實(shí)現(xiàn)一個(gè)多值映射,Map的定義可能是Map<K,Set<V>>,要向Map中放入新值,可通過(guò)如下代碼實(shí)現(xiàn):

Map<Integer, Set<String>> map = new HashMap<>();
// Java7及以前的實(shí)現(xiàn)方式
if(map.containsKey(1)){
 map.get(1).add("one");
}else{
 Set<String> valueSet = new HashSet<String>();
 valueSet.add("one");
 map.put(1, valueSet);
}
// Java8的實(shí)現(xiàn)方式
map.computeIfAbsent(1, v -> new HashSet<String>()).add("yi");

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

computeIfPresent()

該方法簽名為V computeIfPresent(K key, BiFunction<&#63; super K,&#63; super V,&#63; extends V> remappingFunction),作用跟computeIfAbsent()相反,即,只有在當(dāng)前Map中存在key值的映射且非null時(shí),才調(diào)用remappingFunction,如果remappingFunction執(zhí)行結(jié)果為null,則刪除key的映射,否則使用該結(jié)果替換key原來(lái)的映射.

這個(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é)

  1. Java8為容器新增一些有用的方法,這些方法有些是為完善原有功能,有些是為引入函數(shù)式編程,學(xué)習(xí)和使用這些方法有助于我們寫出更加簡(jiǎn)潔有效的代碼.
  2. 函數(shù)接口雖然很多,但絕大多數(shù)時(shí)候我們根本不需要知道它們的名字,書(shū)寫Lambda表達(dá)式時(shí)類型推斷幫我們做了一切.

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI