以前看了許多介紹Java8 Stream的文章,可是初次接觸真的是難以理解(我悟性比較低),沒辦法只能"死記硬背",可是昨天我打王者榮耀(那一局我贏了,牛魔全場MVP)的時候,忽然迸發了靈感,感受以前沒有理解透徹的一會兒就理解透徹了。因此決定用簡單的方式來回憶下我認爲的java8 Stream.java
lambda表達式是Stream API的基石,因此想要學會Stream API的使用,必須先要理解lambda表達式,這裏對lambda作一個簡單回顧。git
咱們經常會看到這樣的代碼github
Arrays.sort(new Integer[]{1, 8, 7, 4}, new Comparator<Integer>() { @Override public int compare(Integer first, Integer second) { return first.compareTo(second); } });
上面這種寫法就是使用了匿名類,咱們常常會使用匿名類的方式,由於咱們只運行一次,不想它一直存在。雖說lambda表達式是爲了什麼所謂的函數式編程,也是你們在社區千呼萬喚纔出來的,可是在我看來就是爲了方(偷)便(懶)。sql
上面的代碼寫着麻煩,可是轉換成下面這樣的呢?編程
Arrays.sort(new Integer[]{1, 8, 7, 4}, (first,second) -> first.compareTo(second));
這樣看着多清爽,並且把一些沒必要要的細節都屏蔽了。對於這種只包含一個抽象方法的接口,你能夠經過lambda接口來建立該接口的對象,這種接口被稱爲函數式接口。api
lambda表達式引入了一個新的操做符:->,它把lambda表達式分爲了2部分app
(n) -> n*n
左側指定表達式所需的參數,若是不須要參數,也能夠爲空。右側是lambda代碼塊,它指定lambda表達式的動做。jvm
須要注意的是若是方法中只有一個返回的時候不用聲明,默認會返回。若是有分支返回的時候須要都進行聲明。ide
(n) -> { if( n <= 10) return n*n; return n * 10; }
有些時候,先要傳遞給其餘代碼的操做已經有實現的方法了。好比GUI中先要在按鈕被點擊時打印event對象,那麼能夠這樣調用函數式編程
button.setOnAction(event -> System.out.println(event));
這個時候我想偷懶,我不想寫event參數,由於只有一個參數,jvm不能幫幫我嗎?下面是修改好的代碼
button.setOnAction(System.out::println);
表達式System.out::println
是一個方法引用,等同於lambda表達式x -> System.out.println(x)
。::操做符將方法名和對象或類的名字分割開來,如下是三種主要的使用狀況:
前兩種狀況,方法引用等同於提供方法參數的lambda表達式。好比Math::pow ==== (x,y) -> Math.pow(x,y)
。
第三種狀況,第一個參數會稱爲執行方法的對象。好比String::compareToIgnoreCase ==== (x,y) -> x.compareToIgnoreCase(y)
。
還有this::equals ==== x -> this.equals(x)
,super::equals ==== super.equals(x)
。
List<String> strList = Arrays.asList("1","2","3"); Stream<Integer> stream = strList.stream().map(Integer::new);
上面代碼的Integer::new
就是構造器引用,不一樣的是在構造器引用中方法名是new。若是存在多個構造器,編譯器會從上下文推斷並找出合適的那一個。
Stream這個單詞翻譯過來就是流的意思,溪流的流,水流的流。
在我看來stream就像是上面的圖同樣,最開始的數據就是小水滴,它通過各類"攔截器"的處理以後,有的小水滴被丟棄,有的變大了,有的加上了顏色,有的變成了三角形。最後它們都變成了帶有顏色的圓。最後被咱們放到結果集中。咱們不少時候寫的代碼是這樣的:遍歷一個集合,而後對集合的元素進行判斷或者轉換,知足條件的加入到新的集合裏面去,這種處理方式就和上面的圖是同樣的。先來看一段代碼
Map<String,Map<String,Integer>> resultMap = new HashMap<>(); Map<String,Integer> maleMap = new HashMap<>(); Map<String,Integer> femaleMap = new HashMap<>(); resultMap.put("male", maleMap); resultMap.put("female",femaleMap); for(int i = 0; i < list.size(); i++) { Person person = list.get(i); String gender = person.getGender(); String level = person.getLevel(); switch (gender) { case "male": Integer maleCount; if("gold".equals(level)) { maleCount = maleMap.get("gold"); maleMap.put("gold", null != maleCount ? maleCount + 1 : 1); } else if("soliver".equals(level)){ maleCount = maleMap.get("soliver"); maleMap.put("soliver", null != maleCount ? maleCount + 1 : 1); } break; case "female": Integer femaleCount; if("gold".equals(level)) { femaleCount = femaleMap.get("gold"); femaleMap.put("gold", null != femaleCount ? femaleCount + 1 : 1); } else if("soliver".equals(level)){ femaleCount = femaleMap.get("soliver"); femaleMap.put("soliver", null != femaleCount ? femaleCount + 1 : 1); } break; } }
上面的代碼做用是統計不一樣性別的工程師職級的人數,在Java StreamAPI出來以前,這樣相似的業務代碼在系統中應該是隨處可見的,手打上面的代碼我大概花了兩分鐘,有了Stream以後,我偷了個懶
Map<String,Map<String,Integer>> result = list.stream().collect( Collectors.toMap( person -> person.getGender(), person -> Collections.singletonMap(person.getLevel(), 1), (existValue,newValue) -> { HashMap<String,Integer> newMap = new HashMap<>(existValue); newValue.forEach((key,value) ->{ if(newMap.containsKey(key)) { newMap.put(key, newMap.get(key) + 1); } else { newMap.put(key, value); } }); return newMap; }) );
或者改爲這樣的代碼
Map<String,Map<String,Integer>> result = stream.collect( Collectors.groupingBy( Person::getGender, Collectors.toMap( person->person.getLevel(), person -> 1, (existValue,newValue) -> existValue + newValue ) ) );
不只代碼塊減小了許多,甚至邏輯也更清晰了。真的是用stream一時爽,一直用一直爽呀。
Stream做爲流,它能夠是有限的能夠是無限的,固然咱們用得最多的仍是有限的流(for循環就是有限的流),如上面那張圖同樣,咱們能夠對流中的元素作各類各樣常見的處理。好比求和,過濾,分組,最大值,最小值等常見處理,因此如今就開始使用Stream吧
函數式接口 | 參數類型 | 返回類型 | 抽象方法名 | 描述 | 其餘方法 |
---|---|---|---|---|---|
Runnable | 無 | void | run | 執行一個沒有參數和返回值的操做 | 無 |
Supplier<T> | 無 | T | get | 提供一個T類型的值 | |
Counsumer<T> | T | void | accept | 處理一個T類型的值 | chain |
BiConsumer<T,U> | T,U | void | accept | 處理T類型和U類型的值 | chain |
Function<T,R> | T | R | apply | 一個參數類型爲T的函數 | compose,andThen,identity |
BiFunction<T,U,R> | T,U | R | apply | 一個參數類型爲T和U的函數 | andThen |
UnaryOperator<T> | T | T | apply | 對類型T進行的一元操做 | compose,andThen,identity |
BinaryOperator<T> | T,T | T | apply | 對類型T進行二元操做 | andThen |
Predicate<T> | T | boolean | test | 一個計算boolean值的函數 | And,or,negate,isEqual |
BiPredicate<T,U> | T,U | boolean | test | 一個含有兩個參數,計算boolean值的函數 | and,or,negate |
使用map方法的時候,至關於對每一個元素應用一個函數,並將返回的值收集到新的Stream中。
Stream<String[]> -> flatMap -> Stream<String> Stream<Set<String>> -> flatMap -> Stream<String> Stream<List<String>> -> flatMap -> Stream<String> Stream<List<Object>> -> flatMap -> Stream<Object> {{1,2}, {3,4}, {5,6} } -> flatMap -> {1,2,3,4,5,6}
Stream上的全部操做分爲兩類:中間操做和結束操做,中間操做只是一種標記(調用到這類方法,並無真正開始流的遍歷。),只有結束操做纔會觸發實際計算。簡單的說就是API返回值仍然是Stream的就是中間操做,不然就是結束操做。
IntStream.of(1,2,3,4,5).fiter(i -> {return i%2 == 0;})
將斷點打在代碼段上便可。IntStream.of(1,2,3,4,5).fiter(MyMath::isDouble)
咱們之前作累加是如何完成的呢?
int sum = 0; for(int value in values) { sum = sum + value; }
如今改爲stream的方式來實現
values.stream().reduce(Integer::sum);
這個reduce()方法就是一個二元函數:從流的前兩個元素開始,不斷將它應用到流中的其餘元素上。
stream API就是爲了方便而設計的,在sql層面並不方便處理的數據能夠經過stream來實現分組,聚合,最大值,最小值,排序,求和等等操做。因此不要把它想得太複雜,只管寫就行了。總有那麼一天你熟練了就能夠寫出簡潔得代碼。或者從如今開始把你項目中的大量for循環改形成stream方式。
原本想寫大段代碼來樣式到stream API的轉換,可是想了想徹底沒有必要,github上找了hutool工具類的部分代碼來完成轉換示例。(能夠經過這種方式來提升stream api的能力)
代碼效果:[a,b,c,c,c] -> a:1,b:1,c:3 Arrays.asList("a","b","c","c","c").stream().collect(Collectors.groupingBy(str->str, Collectors.counting()));
List<String> myList = Arrays.asList("a","b","c","c","c"); myList.stream().collect(Collectors.joining(",","{","}"));
myList.stream().anyMatch(s -> !s.isEmpty());