9102年了,你還在用for迴圈操作集合?
前段時間公司書架多了一本《Java8 實戰》,畢竟久聞lambda的大名,於是借來一閱。這一看,簡直是驚為天人啊,lambda,stream,java8裡簡直是滿腦子騷操作,看我的一愣一愣的。我甚至是第一次感覺到了什麼叫優雅。
本文主要介紹java8中的流處理,看看java8是怎麼愉快的玩耍集合的,讓我們來一起感受java8的魅力吧!
我就隨便舉個例子,看看Stream有多優雅。
// 對蘋果按顏色彙總並績數量Map<String, Long> appleCount = apples.stream() .collect(groupingBy(Apple::getColor, counting()));// 過濾掉顏色為黑色的蘋果,並彙總好蘋果的總金額Double sum = apples.stream() .filter(i->"black".equals(i.getColor())) .collect(toList);
一、lambda表示式
雖然本文重點是stream,但是stream中需要傳遞lambda表示式,所以簡單介紹一下lambda表示式。lambda表示式其實就是匿名函式(anonymous function),是指一類無需定義識別符號的函式或子程式。
java中匿名函式的表現形式,只留下入參和方法體中的內容
// 普通函式public void run(String s){ System.out.print(s+"哈哈"); }// 我不要名字啦!!!(s)->System.out.print(s+"哈哈")
誒,過去我們都用物件調方法的,你弄這個沒名的東西啥時候用啊?
java中我們透過函式式介面來使用這種匿名函式。
函式式介面
1.java中只包含一個未實現方法的介面。其中可以有與Object中同名的方法和預設方法(java8中介面方法可以有預設實現)。
2.java中函式式介面使用@FunctionalInterface進行註解。Runnable、Comparator都是函式式介面。
3.java.util.function包下為我們提供很多常用的函式式介面,例如Function等。
用法舉例:
// 實現Runnable中的run方法,替代匿名內部類。Runnable r = ()->System.out.print("哈哈");// 作為引數傳遞。new Thread(()-> System.out.println("haha")).start(); ArrayList<Apple> list = new ArrayList<>(); list.forEach(i-> System.out.println(i.getWeight()));// 簡化策略模式public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){ List<Apple> apples = new ArrayList<>(); for(Apple apple : inventory){ if(p.test(apple)){ apples.add(apple); } } return apples; }public class BigApple implement ApplePredicate{ @Override public boolean test(Apple a){ if(a.getWeight>10){ return a } } }// 這是個簡單的策略模式,根據使用者的需要,建立不同的介面ApplePredicate實現類,呼叫時傳入不同的實現類就可以,但問題是如果需求過多,建立的實現類也會很多,過於臃腫不方便管理。xx.filterApple(inventory,new BigApple);// 使用lambda表示式,不在需要建立BigApple類xx.filterApple(inventory,i->(i.getWeight>10));
使用lambda表示式可以簡化大量的模板程式碼,並且可以向方法直接傳遞程式碼。
總之
方法出參入參來自函式式介面//入參s,返回void(s)->System.out.println(s);//入參空,返回void()->System.out.print("haha");//入參i,返回i+1i->i+1//後面寫程式碼塊apple->{if(apple.getWeiht>5) return "BIG"; else return "small"; }
好了,不多囉嗦了,如果感興趣推薦下面的文章或《Java8實戰》的前三章。
1.
2.
二、Stream
流是什麼?
Java API的新成員,它允許你使用宣告式方式處理資料集合(類似sql,透過查詢語句表達,而不是臨時編寫一個實現)。
如果有人說lambda表示式不易於理解,那還勉強可以接受(其實過於複雜的lambda缺失不好閱讀,但通常lambda不會做太複雜的實現),但流真的非常的易懂易用。這個語法糖真的是甜死了。
注意事項:
1.流只能使用一次,遍歷結束就代表這個流被消耗掉了
2.流對集合的操作屬於內部迭代,是流幫助我們操作,而不是外部迭代
3.流操作包含:資料來源,中間操作鏈,終端操作三個部分。
基礎流操作
List<Double> collect = list.stream() // 過濾掉黑色的蘋果 .filter(i -> "black".equals(i.getColor())) // 讓蘋果按照重量個價格排序 .sorted(Comparator.comparing(Apple::getWeight) .thenComparing(i->i.getPrice())) // 篩選掉重複的資料 .distinct() // 只要蘋果的價格 .map(Apple::getPrice) // 只留下前兩條資料 .limit(2) // 以集合的形式返回 .collect(toList());// 迴圈列印列表中元素list.forEach(i->System.out.print(i));
Apple::getPrince<=>i -> i.getPrince()
可以看做是僅涉及單一方法的語法糖,效果與lambda表示式相同,但可讀性更好。
同理
下面列表為常見操作
中間
操作 | 型別 | 作用 | 函式描述 | 函式 |
---|---|---|---|---|
filter | 中間 | 過濾 | T -> boolean | Predicate |
sorted | 中間 | 排序 | (T,T)->int | Comparator |
map | 中間 | 對映 | T->R | Function<T,R> |
limit | 中間 | 截斷 | ||
distinct | 中間 | 去重,根據equals方法 | ||
skip | 中間 | 跳過前n個元素 |
終端
操作 | 型別 | 作用 |
---|---|---|
forEach | 終端 | 消費流中的每個元素,使用lambda進行操作 |
count | 終端 | 返回元素個數,long |
collect | 終端 | 將流歸約成一個集合,如List,Map甚至是Integer |
篩選與切片
List<String> strings = Arrays.asList("Hello", "World"); List<String> collect1 = strings.stream() // String對映成String[] .map(i -> i.split("")) // Arrays::Stream 資料陣列,返回一個流String[]->Stream<String> // flatMap各陣列並不分別對映成一個流,而是對映成流的內容 Stream<String>->Stream .flatMap(Arrays::stream) .collect(toList()); System.out.println(collect); ----->輸出 [H, e, l, l, o, W, o, r, l, d]
歸約操作reduce
List<Integer> integers = Arrays.asList(12, 3, 45, 3, 2,-1);// 有初始值的疊加操作Integer reduce = integers.stream().reduce(3, (i, j) -> i + j); Integer reduce2 = integers.stream().reduce(5, (x, y) -> x < y ? x : y);// 無初始值的疊加操作Optional<Integer> reduce1 = integers.stream().reduce((i, j) -> i + j);// 無初始值的最大值Optional<Integer> reduce4 = integers.stream().reduce(Integer::min);// 無初始值的最大值Optional<Integer> reduce5 = integers.stream().reduce(Integer::max);// 求和Optional<Integer> reduce6 = integers.stream().reduce(Integer::sum);
reduce做的事情是取兩個數進行操作,結果返回取下一個數操作,以次類推。
Optional是java8引入的新類,避免造成空指標異常,在集合為空時,結果會包在Optional中,可以用isPresent()方法來判斷是否為空值。
無初始值的情況下可能為空,故返回Optional
中間
操作 | 型別 | 作用 | 函式描述 | 函式 |
---|---|---|---|---|
flatmap | 中間 | 使透過的流返回內容 | T -> boolean | Predicate |
終端
操作 | 型別 | 作用 |
---|---|---|
anyMatch | 終端 | 返回boolean,判斷是否有符合條件內容 |
noneMatch | 終端 | 返回boolean,判斷是否無符合條件內容 |
allMatch | 終端 | 返回boolean,判斷是全為符合條件內容 |
findAny | 終端 | Optional,隨機找一個元素返回 |
findFirst | 終端 | Optional,返回第一個元素 |
reduce | 終端 | Optional(T,T)->T 歸約操作 |
數值流
包裝型別的各種操作都會有拆箱操作和裝箱操作,嚴重影響效能。所以Java8為我們提供了原始數值流。
// 數值流求平均值OptionalDouble average = apples.stream() .mapToDouble(Apple::getPrice) .average();// 數值流求和OptionalDouble average = apples.stream() .mapToDouble(Apple::getPrice) .sum();// 數值流求最大值,沒有則返回2double v = apples.stream() .mapToDouble(Apple::getPrice) .max().orElse(2);// 生成隨機數IntStream s = IntStream.rangeClosed(1,100);
下面列表為常見數值流操作操作
中間
操作 | 型別 | 作用 |
---|---|---|
rangeClosed(1,100) | 中間 | 生成隨機數(1,100] |
range(1,100) | 中間 | 生成隨機數(1,100) |
boxed() | 中間 | 包裝成一般流 |
mapToObj | 中間 | 返回為物件流 |
mapToInt | 中間 | 對映為數值流 |
終端,終端操作與List一般流類似
構建流
-
值建立
Stream<String> s = Stream.of("java","python");
-
陣列建立
int[] i = {2,3,4,5}; Stream<int> = Arrays.stream(i);
-
由檔案生成,NIO API已經更新,以便利用Stream API
Stream<String> s = Files.lines(Paths.get("data.txt"),Charset.defaultCharset());
-
由函式建立流:無限流
// 迭代Stream.iterate(0,n->n+2) .limit(10) .forEach(System.out::println);// 生成,需要傳遞實現Supplier<T>型別的Lambda提供的新值Stream.generate(Math.random) .limit(5) .forEach(System.out::println);
三、總結
至此,本文講述了常見的流操作,目前排序、篩選、求和、歸約等大多數操作我們都能實現了。與過去相比,操作集合變的簡單多了,程式碼也變的更加簡練明瞭。
目前Vert.x,Spring新出的WebFlux都透過lambda表示式來簡化程式碼,不久的將來,非阻塞式框架的大行其道時,lambda表示式必將變的更加重要!
至於開篇見到的分組!!!下篇文章見~
參考資料:
作者:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1762/viewspace-2822181/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 五年了,你還在用Junit4嗎?
- 都9102年了,你還在做“資料搬運工”嗎?
- 都9102年了,還不會Docker?10分鐘帶你從入門操作到實戰上手Docker
- 9102年了,你的簡歷裡還是隻有vue全家桶嗎?Vue
- HTTP/3 都來了,你卻還在用 HTTP/1.1?HTTP
- 還在用Excel維護客戶資訊?你OUT了Excel
- 9102了,你還不會移動端真機除錯?除錯
- 9102年,你已經是個大春節了,你要自己學會用AI了AI
- 你還在用Adapter和ViewHolder寫RecyclerView嗎?Out了!APTView
- 集合------集合框架Collection/Iterator迭代器/增強for迴圈框架
- AirDrop無限迴圈攻擊,你的iPhone還好嗎?AIiPhone
- 都9102年了還不懂動態圖嗎?一文帶你瞭解飛槳動態圖
- 9102 年,完整買斷遊戲還值得嗎?遊戲
- 9102 年了,學點 Docker 知識Docker
- 資料型別——集合與while迴圈資料型別While
- [譯] 2019 年了,為什麼我還在用 jQuery?jQuery
- 你還在Java8中使用迴圈語句嗎?Java
- 還在用迭代器處理集合嗎?試試Stream,真香
- 18. 再說迴圈~列表和迴圈的高階操作
- 迴圈佇列的基本操作佇列
- 你還在用var定義變數嗎?變數
- 還在用Synchronized?Atomic你瞭解不?synchronized
- C# 迴圈時,操作另外一個程式直到操作完成,迴圈繼續執行C#
- 你只用do-while來實現迴圈?太浪費了!While
- #Java裡你還在用double作為價格的欄位型別?你犯了大錯了!Java型別
- 還在用SimpleDateFormat?Java8都發布N年了,轉LocalDateTime吧ORMJavaLDA
- JavaScript的map迴圈、forEach迴圈、filter迴圈、reduce迴圈、reduceRight迴圈JavaScriptFilter
- oracle pl/sql 迴圈比較集合元素是否相同OracleSQL
- 你還在用Kettle嗎?試試這款ETL工具
- for迴圈裡面不要進行remove操作REM
- 迴圈雙連結串列的簡單操作
- C語言——迴圈結構(for迴圈,while迴圈,do-while迴圈)C語言While
- JavaScript 事件迴圈竟還能這樣玩!JavaScript事件
- 大家都在用HTTP/2了,而你還沒聽說過?HTTP
- 無限for迴圈(死迴圈)
- 9102 年的 PHPPHP
- 還在用 Zookeeper 作為註冊中心?小心坑死你!
- 一張圖帶你搞懂Node事件迴圈事件