Java8中有兩大最為重要得改變,其一時Lambda表達式,另外就是 Stream API了。在前面幾篇中簡單學習了Lambda表達式得語法,以及函數式接口。本文就來簡單學習一下Stream API(java.util.stream.*)。
Stream 是 Java8中處理集合得關鍵抽象概念,他可以指定你希望對集合進行得操作,可以執行非常復雜得查找、過濾和映射數據等操作。使用Stream API對集合數據進行操作,就類似使用SQL執行得數據庫查詢。也可以使用S他ream API 來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用得處理數據得方式。
在Stream操作過程中,可以對數據流做過濾,排序,切片等操作,但是操作之后會產生一個新的流,而數據源則不會發生改變。
一、什么是 Stream
Stream是數據渠道,用於操作數據源(集合,數組等)所生成得元素序列。而集合講得是數據,流講得是計算。
注意:
①. Stream 自己不會存儲元素。
②. Stream 不會改變源對象。相反,它會返回一個持有結果得新Stream
③. Stream 操作時延遲執行得,這意味着它們會等到需要結果時才執行。(延遲加載)
二、Stream 操作的三個步驟
1). 創建 Stream
一個數據源(集合,數組),獲取一個流。
2). 中間操作
一個中間操作鏈,對數據源的數據進行處理。
3). 終止操作
一個終止操作,執行中間操作鏈,並產生結果。
三、創建Stream 的四種方式
1). 通過Collection得Stream()方法(串行流)或者 parallelStream()方法(並行流)創建Stream。
1 /** 2 * 創建 Stream的四種方式 3 * 1.通過Collection得Stream()方法(串行流) 4 或者 parallelStream()方法(並行流)創建Stream 5 */ 6 @Test 7 public void test1 () { 8 9 //1. 通過Collection得Stream()方法(串行流) 10 //或者 parallelStream()方法(並行流)創建Stream 11 List<String> list = new ArrayList<String>(); 12 Stream<String> stream1 = list.stream(); 13 14 Stream<String> stream2 = list.parallelStream(); 15 16 }
2).通過Arrays中得靜態方法stream()獲取數組流
1 /** 2 * 創建 Stream的四種方式 3 * 2. 通過Arrays中得靜態方法stream()獲取數組流 4 */ 5 @Test 6 public void test2 () { 7 8 //2. 通過Arrays中得靜態方法stream()獲取數組流 9 IntStream stream = Arrays.stream(new int[]{3,5}); 10 11 }
3). 通過Stream類中得 of()靜態方法獲取流
1 /** 2 * 創建 Stream的四種方式 3 * 3. 通過Stream類中得 of()靜態方法獲取流 4 */ 5 @Test 6 public void test3 () { 7 8 //3. 通過Stream類中得 of()靜態方法獲取流 9 Stream<String> stream = Stream.of("4645", "huinnj"); 10 11 }
4). 創建無限流(迭代、生成)
1 /** 2 * 創建 Stream的四種方式 3 * 4. 創建無限流(迭代、生成) 4 */ 5 @Test 6 public void test4 () { 7 8 //4. 創建無限流 9 //迭代(需要傳入一個種子,也就是起始值,然后傳入一個一元操作) 10 Stream<Integer> stream1 = Stream.iterate(2, (x) -> x * 2); 11 12 //生成(無限產生對象) 13 Stream<Double> stream2 = Stream.generate(() -> Math.random()); 14 15 }
四、Stream 中間操作
多個中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何得處理!而終止操作時一次性全部處理,稱為‘延遲加載’
1). 篩選與切片
①. filter —— 接收Lambda ,從流中排除某些元素。
1 /** 2 * 篩選與切片 3 * filter —— 接收Lambda ,從流中排除某些元素。 4 * 5 */ 6 @Test 7 public void test5 () { 8 //內部迭代:在此過程中沒有進行過迭代,由Stream api進行迭代 9 //中間操作:不會執行任何操作 10 Stream<Person> stream = list.stream().filter((e) -> { 11 System.out.println("Stream API 中間操作"); 12 return e.getAge() > 30; 13 }); 14 15 //終止操作:只有執行終止操作才會執行全部。即:延遲加載 16 stream.forEach(System.out :: println); 17 18 }
執行上面方法,得到下面結果。
Person [name=張三, sex=男, age=76] Stream API 中間操作 Stream API 中間操作 Person [name=王五, sex=男, age=35] Stream API 中間操作 Stream API 中間操作 Person [name=錢七, sex=男, age=56] Stream API 中間操作 Person [name=翠花, sex=女, age=34]
我們,在執行終止語句之后,一邊迭代,一邊打印,而我們並沒有去迭代上面集合,其實這是內部迭代,由Stream API 完成。
下面我們來看看外部迭代,也就是我們人為得迭代。
1 @Test 2 public void test6 () { 3 //外部迭代 4 Iterator<Person> it = list.iterator(); 5 while (it.hasNext()) { 6 System.out.println(it.next()); 7 } 8 9 }
②. limit —— 截斷流,使其元素不超過給定數量。
1 /** 2 * limit —— 截斷流,使其元素不超過給定數量。 3 */ 4 @Test 5 public void test7 () { 6 //過濾之后取2個值 7 list.stream().filter((e) -> e.getAge() >30 ). 8 limit(2).forEach(System.out :: println); 9 10 }
在這里,我們可以配合其他得中間操作,並截斷流,使我們可以取得相應個數得元素。而且在上面計算中,只要發現有2條符合條件得元素,則不會繼續往下迭代數據,可以提高效率。
2). 跳過元素
skip(n),返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空,與limit(n)互補。
1 /** 2 * skip(n)—— 跳過元素,返回一個扔掉了前n個元素的流。 3 * 若流中元素不足n個,則返回一個空,與limit(n)互補。 4 */ 5 @Test 6 public void test8 () { 7 //跳過前2個值 8 list.stream().skip(2).forEach(System.out :: println); 9 10 } 11
3). 篩選
distinct 通過流所生成元素的hashCode()和equals()去除重復元素
1 /** 2 * distinct —— 篩選,通過流所生成元素的hashCode()和equals()去除重復元素 3 */ 4 @Test 5 public void test9 () { 6 7 list.stream().distinct().forEach(System.out :: println); 8 9 }
注意:distinct 需要實體中重寫hashCode()和 equals()方法才可以使用
4). 映射
① . map ,將元素轉換成其他形式或者提取信息。接收一個函數作為參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。
1 /** 2 * map —— 映射 ,將元素轉換成其他形式或者提取信息。接收一個函數作為參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。 3 */ 4 @Test 5 public void test10 () { 6 //將流中每一個元素都映射到map的函數中,每個元素執行這個函數,再返回 7 List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd"); 8 list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf); 9 10 //獲取Person中的每一個人得名字name,再返回一個集合 11 List<String> names = this.list.stream().map(Person :: getName). 12 collect(Collectors.toList()); 13 }
② . flatMap —— 接收一個函數作為參數,將流中的每個值都換成一個流,然后把所有流連接成一個流
1 /** 2 * flatMap —— 接收一個函數作為參數,將流中的每個值都換成一個流,然后把所有流連接成一個流 3 */ 4 @Test 5 public void test11 () { 6 StreamAPI_Test s = new StreamAPI_Test(); 7 List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd"); 8 list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println); 9 10 //如果使用map則需要這樣寫 11 list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> { 12 e.forEach(System.out::println); 13 }); 14 } 15 16 /** 17 * 將一個字符串轉換為流 18 * @param str 19 * @return 20 */ 21 public Stream<Character> filterCharacter(String str){ 22 List<Character> list = new ArrayList<>(); 23 for (Character ch : str.toCharArray()) { 24 list.add(ch); 25 } 26 return list.stream(); 27 }
其實map方法就相當於Collaction的add方法,如果add的是個集合得話就會變成二維數組,而flatMap 的話就相當於Collaction的addAll方法,參數如果是集合得話,只是將2個集合合並,而不是變成二維數組。
5). 排序
sorted有兩種方法,一種是不傳任何參數,叫自然排序,還有一種需要傳Comparator 接口參數,叫做定制排序。
1 /** 2 * sorted有兩種方法,一種是不傳任何參數,叫自然排序,還有一種需要傳Comparator 接口參數,叫做定制排序。 3 */ 4 @Test 5 public void test12 () { 6 // 自然排序 7 List<Person> persons = list.stream().sorted().collect(Collectors.toList()); 8 9 //定制排序 10 List<Person> persons1 = list.stream().sorted((e1, e2) -> { 11 if (e1.getAge() == e2.getAge()) { 12 return 0; 13 } else if (e1.getAge() > e2.getAge()) { 14 return 1; 15 } else { 16 return -1; 17 } 18 }).collect(Collectors.toList()); 19 }
五、Stream 終止操作
1). 查找與匹配
首先我們先創建一個集合。
1 List<Person> persons = Arrays.asList( 2 new Person("張三", "男", 76, Status.FREE), 3 new Person("李四", "女", 12, Status.BUSY), 4 new Person("王五", "男", 35, Status.BUSY), 5 new Person("趙六", "男", 3, Status.FREE), 6 new Person("錢七", "男", 56, Status.BUSY), 7 new Person("翠花", "女", 34, Status.VOCATION), 8 new Person("翠花", "女", 34, Status.FREE), 9 new Person("翠花", "女", 34, Status.VOCATION) 10 );
①. allMatch —— 檢查是否匹配所有元素。
1 /** 2 * allMatch —— 檢查是否匹配所有元素。 3 * 判斷所有狀態是否都是FREE 4 */ 5 @Test 6 public void test13 () { 7 boolean b = persons.stream().allMatch((e) -> Status.FREE.equals(e.getStatus())); 8 System.out.println(b); 9 }
②. anyMatch —— 檢查是否至少匹配所有元素。
1 /** 2 * anyMatch —— 檢查是否至少匹配所有元素。 3 * 判斷是否有一個是FREE 4 */ 5 @Test 6 public void test14 () { 7 boolean b = persons.stream().anyMatch((e) -> Status.FREE.equals(e.getStatus())); 8 System.out.println(b); 9 }
③. noneMatch —— 檢查是否沒有匹配所有元素。
1 /** 2 * noneMatch —— 檢查是否沒有匹配所有元素。 3 * 判斷是否沒有FREE 4 */ 5 @Test 6 public void test15 () { 7 boolean b = persons.stream().noneMatch((e) -> Status.FREE.equals(e.getStatus())); 8 System.out.println(b); 9 }
④. findFirst —— 返回第一個元素。
1 /** 2 * findFirst —— 返回第一個元素。 3 * 4 */ 5 @Test 6 public void test16 () { 7 Optional<Person> person = persons.stream().findFirst(); 8 System.out.println(person); 9 10 person.orElse(new Person("王五", "男", 35, Status.BUSY)); 11 }
注意:上面findFirst 返回的是一個Optional的對像,他將我們的Person封裝了一層,這是為了避免空指針。而且這個對象為我們提供了一個orElse方法,就是當我們得到的這個對象為空時,我們可以傳入一個新得對象去替代它。
⑤. findAny —— 返回當前流中任意元素。
1 /** 2 * findAny —— 返回當前流中任意元素。 3 */ 4 @Test 5 public void test17 () { 6 Optional<Person> person = persons.stream().findAny(); 7 System.out.println(person); 8 9 person.orElse(new Person("王五", "男", 35, Status.BUSY)); 10 }
⑥. count —— 返回流中元素總個數。
1 /** 2 * count —— 返回流中元素總個數。 3 */ 4 @Test 5 public void test18 () { 6 long count = persons.stream().count(); 7 System.out.println(count); 8 9 }
⑦. max —— 返回流中最大值。
1 /** 2 * max —— 返回流中最大值。 3 */ 4 @Test 5 public void test18 () { 6 Optional<Person> person = persons.stream().max((e1, e2) -> Double.compare(e1.getAge(), e2.getAge())); 7 System.out.println(person); 8 9 }
⑧. min —— 返回流中最小值。
1 /** 2 * min —— 返回流中最小值。 3 */ 4 @Test 5 public void test20 () { 6 Optional<Person> person = persons.stream().min((e1, e2) -> Double.compare(e1.getAge(), e2.getAge())); 7 System.out.println(person); 8 9 }
2). 歸約(可以將流中元素反復結合在一起,得到一個值)
①. reduce(T identitty,BinaryOperator)首先,需要傳一個起始值,然后,傳入的是一個二元運算。
1 /** 2 * reduce(T identitty,BinaryOperator)首先,需要傳一個起始值,然后,傳入的是一個二元運算。 3 */ 4 @Test 5 public void test21 () { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Integer sum = list.stream().reduce(0, (x, y) -> x + y); 8 }
②. reduce(BinaryOperator)此方法相對於上面方法來說,沒有起始值,則有可能結果為空,所以返回的值會被封裝到Optional中。
1 /** 2 * reduce(BinaryOperator)此方法相對於上面方法來說,沒有起始值,則有可能結果為空,所以返回的值會被封裝到Optional中 3 */ 4 @Test 5 public void test22 () { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Optional<Integer> sum = list.stream().reduce(Integer :: sum); 8 }
備注:map和reduce的連接通常稱為map-reduce模式,因Google用它來進行網絡搜索而出名。
3). 收集collect(將流轉換為其他形式。接收一個Collector接口得實現,用於給其他Stream中元素做匯總的方法)
Collector接口中方法得實現決定了如何對流執行收集操作(如收集到List,Set,Map)。但是Collectors實用類提供了很多靜態方法,可以方便地創建常見得收集器實例。
①. Collectors.toList() 將流轉換成List
1 /** 2 * Collectors.toList() 將流轉換成List 3 */ 4 @Test 5 public void test23() { 6 7 List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList()); 8 }
②. Collectors.toSet()將流轉換為Set
1 /** 2 * Collectors.toSet() 將流轉換成Set 3 */ 4 @Test 5 public void test24() { 6 7 Set<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toSet()); 8 }
③. Collectors.toCollection()將流轉換為其他類型的集合
1 /** 2 * Collectors.toCollection()將流轉換為其他類型的集合 3 */ 4 @Test 5 public void test25() { 6 7 LinkedList<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toCollection(LinkedList :: new)); 8 }
④. Collectors.counting() 元素個數
1 /** 2 * Collectors.counting() 總數 3 */ 4 @Test 5 public void test26() { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Long count = list.stream().collect(Collectors.counting()); 8 }
⑤. Collectors.averagingDouble()、Collectors.averagingDouble()、Collectors.averagingLong() 平均數,這三個方法都可以求平均數,不同之處在於傳入得參數類型不同,返回值都為Double
1 /** 2 * Collectors.averagingInt() 、 3 * Collectors.averagingDouble()、 4 * Collectors.averagingLong() 平均數, 5 * 者三個方法都可以求平均數,不同之處在於傳入得參數類型不同, 6 * 返回值都為Double 7 */ 8 @Test 9 public void test27() { 10 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 11 Double avg = list.stream().collect(Collectors.averagingInt((x) -> x)); 12 }
⑥. Collectors.summingDouble()、Collectors.summingDouble()、Collectors.summingLong() 求和,不同之處在於傳入得參數類型不同,返回值為Integer, Double, Long
1 /** 2 * Collectors.summingInt() 、 3 * Collectors.summingDouble()、 4 * Collectors.summingLong() 求和, 5 * 者三個方法都可以求總數,不同之處在於傳入得參數類型不同, 6 * 返回值為Integer, Double, Long 7 */ 8 @Test 9 public void test28() { 10 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 11 Integer sum = list.stream().collect(Collectors.summingInt((x) -> x)); 12 }
⑦. Collectors.maxBy() 求最大值
1 /** 2 * Collectors.maxBy() 求最大值 3 */ 4 @Test 5 public void test29() { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Optional<Integer> max = list.stream().collect(Collectors.maxBy((x, y) ->Integer.compare(x, y))); 8 }
⑧. Collectors.minBy() 求最小值
/** * Collectors.minBy() 求最小值 */ @Test public void test29() { List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Optional<Integer> min = list.stream().collect(Collectors.minBy((x, y) ->Integer.compare(x, y))); }
⑨. Collectors.groupingBy()分組 ,返回一個map
1 /** 2 * Collectors.groupingBy()分組 ,返回一個map 3 */ 4 @Test 5 public void test30() { 6 Map<String, List<Person>> personMap = list.stream().collect(Collectors.groupingBy(Person :: getSex)); 7 }
Collectors.groupingBy()還可以實現多級分組
1 /** 2 * Collectors.groupingBy()多級分組 ,返回一個map 3 */ 4 @Test 5 public void test31() { 6 Map<String, Map<Status, List<Person>>> personMap = list.stream().collect(Collectors.groupingBy(Person :: getSex, Collectors.groupingBy(Person :: getStatus))); 7 }
⑩. Collectors.partitioningBy() 分區,參數中傳一個函數,返回true,和false 分成兩個區
1 /** 2 * Collectors.partitioningBy() 分區,參數中傳一個函數,返回true,和false 分成兩個區 3 */ 4 @Test 5 public void test32() { 6 Map<Boolean, List<Person>> personMap = list.stream().collect(Collectors.partitioningBy((x) -> x.getAge() > 30)); 7 }
上面就是Stream的一些基本操作,只要勤加練習就可以靈活使用,而且效率大大提高。