本文github地址java
Java8爲容器新增一些有用的方法,這些方法有些是爲完善原有功能,有些是爲引入函數式編程(Lambda表達式),學習和使用這些方法有助於咱們寫出更加簡潔有效的代碼.本文分別以ArrayList和HashMap爲例,講解Java8集合框架(Java Collections Framework)中新加入方法的使用.git
咱們先從最熟悉的Java集合框架(Java Collections Framework, JCF)開始提及。程序員
爲引入Lambda表達式,Java8新增了java.util.funcion
包,裏面包含經常使用的函數接口,這是Lambda表達式的基礎,Java集合框架也新增部分接口,以便與Lambda表達式對接。github
首先回顧一下Java集合框架的接口繼承結構:編程
上圖中綠色標註的接口類,表示在Java8中加入了新的接口方法,固然因爲繼承關係,他們相應的子類也都會繼承這些新方法。下表詳細列舉了這些方法。markdown
接口名 | 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
實現原理,將有助於理解下文。app
該方法的簽名爲void forEach(Consumer<? super E> action)
,做用是對容器中的每一個元素執行action
指定的動做,其中Consumer
是個函數接口,裏面只有一個待實現方法void accept(T t)
(後面咱們會看到,這個方法叫什麼根本不重要,你甚至不須要記憶它的名字)。框架
需求:假設有一個字符串列表,須要打印出其中全部長度大於3的字符串.ide
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()結合匿名內部類迭代 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); } });
上述代碼調用forEach()
方法,並使用匿名內部類實現Comsumer
接口。到目前爲止咱們沒看到這種設計有什麼好處,可是不要忘記Lambda表達式,使用Lambda表達式實現以下:
// 使用forEach()結合Lambda表達式迭代 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.forEach( str -> { if(str.length()>3) System.out.println(str); });
上述代碼給forEach()
方法傳入一個Lambda表達式,咱們不須要知道accept()
方法,也不須要知道Consumer
接口,類型推導幫咱們作了一切。
該方法簽名爲boolean removeIf(Predicate<? super E> filter)
,做用是刪除容器中全部知足filter
指定條件的元素,其中Predicate
是一個函數接口,裏面只有一個待實現方法boolean test(T t)
,一樣的這個方法的名字根本不重要,由於用的時候不須要書寫這個名字。
需求:假設有一個字符串列表,須要刪除其中全部長度大於3的字符串。
咱們知道若是須要在迭代過程衝對容器進行刪除操做必須使用迭代器,不然會拋出ConcurrentModificationException
,因此上述任務傳統的寫法是:
// 使用迭代器刪除列表元素 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); Iterator<String> it = list.iterator(); while(it.hasNext()){ if(it.next().length()>3) // 刪除長度大於3的元素 it.remove(); }
如今使用removeIf()
方法結合匿名內部類,咱們但是這樣實現:
// 使用removeIf()結合匿名名內部類實現 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.removeIf(new Predicate<String>(){ // 刪除長度大於3的元素 @Override public boolean test(String str){ return str.length()>3; } });
上述代碼使用removeIf()
方法,並使用匿名內部類實現Precicate
接口。相信你已經想到用Lambda表達式該怎麼寫了:
// 使用removeIf()結合Lambda表達式實現 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.removeIf(str -> str.length()>3); // 刪除長度大於3的元素
使用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()
方法結合匿名內部類能夠實現以下:
// 使用匿名內部類實現 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; } });
上述代碼調用replaceAll()
方法,並使用匿名內部類實現UnaryOperator
接口。咱們知道能夠用更爲簡潔的Lambda表達式實現:
// 使用Lambda表達式實現 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.replaceAll(str -> { if(str.length()>3) return str.toUpperCase(); return str; });
該方法定義在List
接口中,方法簽名爲void sort(Comparator<? super E> c)
,該方法根據c
指定的比較規則對容器元素進行排序。Comparator
接口咱們並不陌生,其中有一個方法int compare(T o1, T o2)
須要實現,顯然該接口是個函數接口。
需求:假設有一個字符串列表,按照字符串長度增序對元素排序。
因爲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(); } });
如今能夠直接使用List.sort()方法
,結合Lambda表達式,能夠這樣寫:
// List.sort()方法結合Lambda表達式 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.sort((str1, str2) -> str1.length()-str2.length());
方法簽名爲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以及以前迭代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()
方法,結合匿名內部類,代碼以下:
// 使用forEach()結合匿名內部類迭代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); } });
上述代碼調用forEach()
方法,並使用匿名內部類實現BiConsumer
接口。固然,實際場景中沒人使用匿名內部類寫法,由於有Lambda表達式:
// 使用forEach()結合Lambda表達式迭代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)); }
該方法跟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()結合匿名內部類實現 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(); } });
上述代碼調用replaceAll()
方法,並使用匿名內部類實現BiFunction
接口。更進一步的,使用Lambda表達式實現以下:
// 使用replaceAll()結合Lambda表達式實現 HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.replaceAll((k, v) -> v.toUpperCase());
簡潔到讓人難以置信.
該方法簽名爲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()
方法雖然語義有些複雜,但該方法的用方式很明確,一個比較常見的場景是將新的錯誤信息拼接到原來的信息上,好比:
map.merge(key, newMsg, (v1, v2) -> v1+v2);
該方法簽名爲compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
,做用是把remappingFunction
的計算結果關聯到key
上,若是計算結果爲null
,則在Map
中刪除key
的映射.
要實現上述merge()
方法中錯誤信息拼接的例子,使用compute()
代碼以下:
map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));
該方法簽名爲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
中放入新值,可經過以下代碼實現:
Map<Integer, Set<String>> map = new HashMap<>(); // Java7及之前的實現方式 if(map.containsKey(1)){ map.get(1).add("one"); }else{ Set<String> valueSet = new HashSet<String>(); valueSet.add("one"); map.put(1, valueSet); } // Java8的實現方式 map.computeIfAbsent(1, v -> new HashSet<String>()).add("yi");
使用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;