本文github地址
java8為容器新增一些有用的方法,這些方法有些是為完善原有功能,有些是為引入函數式編程(Lambda表達式),學習和使用這些方法有助于我們寫出更加簡潔有效的代碼.本文分別以ArrayList和HashMap為例,講解Java8集合框架(Java Collections Framework)中新加入方法的使用.
我們先從最熟悉的Java集合框架(Java Collections Framework, JCF)開始說起。
為引入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和List新加入了一些方法,我們以是List的子類ArrayList為例來說明。了解Java7ArrayList實現原理,將有助于理解下文。
該方法的簽名為void forEach(Consumer<? super E> action),作用是對容器中的每個元素執行action指定的動作,其中Consumer是個函數接口,里面只有一個待實現方法void accept(T t)(后面我們會看到,這個方法叫什么根本不重要,你甚至不需要記憶它的名字)。
需求:假設有一個字符串列表,需要打印出其中所有長度大于3的字符串.
Java7及以前我們可以用增強的for循環實現:
// 使用曾強for循環迭代ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));for(String str : list){ if(str.length()>3) System.out.println(str);}現在使用forEach()方法結合匿名內部類,可以這樣實現:
上述代碼調用forEach()方法,并使用匿名內部類實現Comsumer接口。到目前為止我們沒看到這種設計有什么好處,但是不要忘記Lambda表達式,使用Lambda表達式實現如下:
上述代碼給forEach()方法傳入一個Lambda表達式,我們不需要知道accept()方法,也不需要知道Consumer接口,類型推導幫我們做了一切。
該方法簽名為boolean removeIf(Predicate<? super E> filter),作用是刪除容器中所有滿足filter指定條件的元素,其中Predicate是一個函數接口,里面只有一個待實現方法boolean test(T t),同樣的這個方法的名字根本不重要,因為用的時候不需要書寫這個名字。
需求:假設有一個字符串列表,需要刪除其中所有長度大于3的字符串。
我們知道如果需要在迭代過程沖對容器進行刪除操作必須使用迭代器,否則會拋出ConcurrentModificationException,所以上述任務傳統的寫法是:
現在使用removeIf()方法結合匿名內部類,我們可是這樣實現:
上述代碼使用removeIf()方法,并使用匿名內部類實現Precicate接口。相信你已經想到用Lambda表達式該怎么寫了:
使用Lambda表達式不需要記憶Predicate接口名,也不需要記憶test()方法名,只需要知道此處需要一個返回布爾類型的Lambda表達式就行了。
該方法簽名為void replaceAll(UnaryOperator<E> operator),作用是對每個元素執行operator指定的操作,并用操作結果來替換原來的元素。其中UnaryOperator是一個函數接口,里面只有一個待實現函數T apply(T t)。
需求:假設有一個字符串列表,將其中所有長度大于3的元素轉換成大寫,其余元素不變。
Java7及之前似乎沒有優雅的辦法:
// 使用下標實現元素替換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()方法結合匿名內部類可以實現如下:
上述代碼調用replaceAll()方法,并使用匿名內部類實現UnaryOperator接口。我們知道可以用更為簡潔的Lambda表達式實現:
該方法定義在List接口中,方法簽名為void sort(Comparator<? super E> c),該方法根據c指定的比較規則對容器元素進行排序。Comparator接口我們并不陌生,其中有一個方法int compare(T o1, T o2)需要實現,顯然該接口是個函數接口。
需求:假設有一個字符串列表,按照字符串長度增序對元素排序。
由于Java7以及之前sort()方法在Collections工具類中,所以代碼要這樣寫:
現在可以直接使用List.sort()方法,結合Lambda表達式,可以這樣寫:
方法簽名為Spliterator<E> spliterator(),該方法返回容器的可拆分迭代器。從名字來看該方法跟iterator()方法有點像,我們知道Iterator是用來迭代容器的,Spliterator也有類似作用,但二者有如下不同:
Spliterator既可以像Iterator那樣逐個迭代,也可以批量迭代。批量迭代可以降低迭代的開銷。Spliterator是可拆分的,一個Spliterator可以通過調用Spliterator<T> trySplit()方法來嘗試分成兩個。一個是this,另一個是新返回的那個,這兩個迭代器代表的元素沒有重疊。可通過(多次)調用Spliterator.trySplit()方法來分解負載,以便多線程處理。
stream()和parallelStream()分別返回該容器的Stream視圖表示,不同之處在于parallelStream()返回并行的Stream。Stream是Java函數式編程的核心類,我們會在后面章節中學習。
相比Collection,Map中加入了更多的方法,我們以HashMap為例來逐一探秘。了解Java7HashMap實現原理,將有助于理解下文。
該方法簽名為void forEach(BiConsumer<? super K,? super V> action),作用是對Map中的每個映射執行action指定的操作,其中BiConsumer是一個函數接口,里面有一個待實現方法void accept(T t, U u)。BinConsumer接口名字和accept()方法名字都不重要,請不要記憶他們。
需求:假設有一個數字到對應英文單詞的Map,請輸出Map中的所有映射關系.
Java7以及之前經典的代碼如下:
// Java7以及之前迭代MapHashMap<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()方法,結合匿名內部類,代碼如下:
上述代碼調用forEach()方法,并使用匿名內部類實現BiConsumer接口。當然,實際場景中沒人使用匿名內部類寫法,因為有Lambda表達式:
該方法跟Lambda表達式沒關系,但是很有用。方法簽名為V getOrDefault(Object key, V defaultValue),作用是按照給定的key查詢Map中對應的value,如果沒有找到則返回defaultValue。使用該方法程序員可以省去查詢指定鍵值是否存在的麻煩.
需求;假設有一個數字到對應英文單詞的Map,輸出4對應的英文單詞,如果不存在則輸出NoValue
// 查詢Map中指定的值,不存在時使用默認值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該方法跟Lambda表達式沒關系,但是很有用。方法簽名為V putIfAbsent(K key, V value),作用是只有在不存在key值的映射或映射值為null時,才將value指定的值放入到Map中,否則不對Map做更改.該方法將條件判斷和賦值合二為一,使用起來更加方便.
我們都知道Map中有一個remove(Object key)方法,來根據指定key值刪除Map中的映射關系;Java8新增了remove(Object key, Object value)方法,只有在當前Map中key正好映射到value時才刪除該映射,否則什么也不做.
在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去替換原來的值,否則什么也不做.該方法簽名為replaceAll(BiFunction<? super K,? super V,? extends V> function),作用是對Map中的每個映射執行function指定的操作,并用function的執行結果替換原來的value,其中BiFunction是一個函數接口,里面有一個待實現方法R apply(T t, U u).不要被如此多的函數接口嚇到,因為使用的時候根本不需要知道他們的名字.
需求:假設有一個數字到對應英文單詞的Map,請將原來映射關系中的單詞都轉換成大寫.
Java7以及之前經典的代碼如下:
// 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()){ entry.setValue(entry.getValue().toUpperCase());}使用replaceAll()方法結合匿名內部類,實現如下:
上述代碼調用replaceAll()方法,并使用匿名內部類實現BiFunction接口。更進一步的,使用Lambda表達式實現如下:
簡潔到讓人難以置信.
該方法簽名為merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction),作用是:
Map中key對應的映射不存在或者為null,則將value(不能是null)關聯到key上;否則執行remappingFunction,如果執行結果非null則用該結果跟key關聯,否則在Map中刪除key的映射.參數中BiFunction函數接口前面已經介紹過,里面有一個待實現方法R apply(T t, U u).
merge()方法雖然語義有些復雜,但該方法的用方式很明確,一個比較常見的場景是將新的錯誤信息拼接到原來的信息上,比如:
該方法簽名為compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),作用是把remappingFunction的計算結果關聯到key上,如果計算結果為null,則在Map中刪除key的映射.
要實現上述merge()方法中錯誤信息拼接的例子,使用compute()代碼如下:
該方法簽名為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中放入新值,可通過如下代碼實現:
使用computeIfAbsent()將條件判斷和添加操作合二為一,使代碼更加簡潔.
該方法簽名為V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),作用跟computeIfAbsent()相反,即,只有在當前Map中存在key值的映射且非null時,才調用remappingFunction,如果remappingFunction執行結果為null,則刪除key的映射,否則使用該結果替換key原來的映射.
這個函數的功能跟如下代碼是等效的:
// 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;本文github地址
新聞熱點
疑難解答