一. 前言
隨著Java的發展,越來越多的企業開始使用JDK1.8 版本,JDK1.8 是自 JDK1.5之后最重要的版本,這個版本包含語言、編譯器、庫、工具、JVM等方面的十多個新特性,本次文章將著重學習Stream,
Stream 是JDK1.8 中處理集合的關鍵抽象概念,Lambda 和 Stream 是JDK1.8新增的函式式編程最有亮點的特性了,它可以指定你希望對集合進行的操作,可以執行非常復雜的查找、過濾和映射資料等操作,使用Stream API 對集合資料進行操作,就類似于使用SQL執行的資料庫查詢,Stream 使用一種類似用 SQL 陳述句從資料庫查詢資料的直觀方式來提供一種對 Java 集合運算和表達的高階抽象,Stream API可以極大提高Java程式員的生產力,讓程式員寫出高效率、干凈、簡潔的代碼,
這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等,
元素流在管道中經過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結果,
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
簡而言之,Stream API提供了一種高效且易于使用的處理資料的方式,
二. 什么是Stream
1.Stream(流)是一個來自資料源的元素佇列并支持聚合操作,
- 元素是特定型別的物件,形成一個佇列, Java中的Stream并不會存盤元素,而是按需計算,
- 資料源 流的來源, 可以是集合,陣列,I/O channel, 產生器generator 等,
- 聚合操作 類似SQL陳述句一樣的操作, 比如filter, map, reduce, find, match, sorted等,
2.和以前的Collection操作不同, Stream操作還有兩個基礎的特征:
- Pipelining: 中間操作都會回傳流物件本身, 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style), 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting),
- 內部迭代: 以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代, Stream提供了內部迭代的方式, 通過訪問者模式(Visitor)實作,
特點:
- Stream 不是資料結構,不會保存資料,
- Stream 不會修改原來的資料源,它會將操作后的資料保存到另外一個物件中,(保留意見:畢竟peek方法可以修改流中元素)
- 惰性求值,流在中間處理程序中,只是對操作進行了記錄,并不會立即執行,需要等到執行終止操作的時候才會進行實際的計算,
三. 關于Stream API
1. Stream API分類
Stream 操作分為中間操作或者終止操作兩種,終止操作回傳一特定型別的計算結果,而中間操作回傳Stream本身,Stream 的操作型別具體分類如下:

解釋:
-
無狀態:指元素的處理不受之前元素的影響;
-
有狀態:指該操作只有拿到所有元素之后才能繼續下去,
-
非短路操作:指必須處理所有元素才能得到最終結果;
-
短路操作:指遇到某些符合條件的元素就可以得到最終結果,如 A || B,只要A為true,則無需判斷B的結果,
2. 如何使用Stream流?
使用Stream流分為三步,
-
創建Stream
一個資料源(如:集合、陣列),獲取一個Stream流,
-
中間操作
一個中間操作鏈,對資料源(如:集合、陣列)的資料進行處理,
-
終止操作
一個終止操作,執行中間操作鏈,并產生一個計算結果,
3. Stream的中間操作和結束操作
- 中間操作
filter: 過濾流,過濾流中的元素,回傳一個符合條件的Stream
map: 轉換流,將一種型別的流轉換為另外一種流,(mapToInt、mapToLong、mapToDouble 回傳int、long、double基本型別對應的Stream)
flatMap:簡單的說,就是一個或多個流合并成一個新流,(flatMapToInt、flatMapToLong、flatMapToDouble 回傳對應的IntStream、LongStream、DoubleStream流,)
distinct: 回傳去重的Stream,
sorted: 回傳一個排序的Stream,
peek: 主要用來查看流中元素的資料狀態,
limit: 回傳前n個元素資料組成的Stream,屬于短路操作
skip: 回傳第n個元素后面資料組成的Stream,
- 結束操作
forEach: 回圈操作Stream中資料,
toArray: 回傳流中元素對應的陣列物件,
reduce: 聚合操作,用來做統計,
collect: 聚合操作,封裝目標資料,
min、max、count: 聚合操作,最小值,最大值,總數量,
anyMatch: 短路操作,有一個符合條件回傳true,
allMatch: 所有資料都符合條件回傳true,
noneMatch: 所有資料都不符合條件回傳true,
findFirst: 短路操作,獲取第一個元素,
findAny: 短路操作,獲取任一元素,
forEachOrdered: 暗元素順序執行回圈操作,
四. 如何獲取Stream流
在 Java 8 中, 集合介面有兩個方法來生成流:
- stream() ? 為集合創建串行流,
- parallelStream() ? 為集合創建并行流,
1. 常見幾種集合流的創建
/**
* stream,獲取各種集合的stream流
*/
@Test
public void testCollectionStream(){
//List集合
List<String> stringList = new ArrayList<>();
//Set集合
Set<String> stringSet = new HashSet<>();
//Map集合
Map<String,Object> stringObjectMap = new HashMap<>();
//陣列
String[] stringArray = {"張三三","李四","王五","王五","趙八",};
//通過list獲取stream流
Stream<String> streamList = stringList.stream();
//通過set獲取stream流
Stream<String> streamSet = stringSet.stream();
//通過map獲取stream流
Stream<String> streamMap = stringObjectMap.keySet().stream();
//通過array獲取stream流
Stream<String> streamArray1 = Stream.of(stringArray);
}
2. 構造流的幾種常見方法
@Test
public void testCollectionStream(){
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String[] strArray = new String[]{"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
}
五. Stream在代碼中的使用方式
關于Stream的常見操作方式,主要分為兩大類: 中間操作和終止操作 ,接下來就通過這兩大分類,講解下具體的語法用法,
1. 流的中間操作
1.1 篩選過濾
-
filter:過濾流中的某些元素
/** * filter 方法 , 回傳符合過濾條件的值 */ @Test public void testFilter() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.stream().filter(e -> e.contains("張")).forEach(System.out::println); }或
- filter多個過濾篩選條件
/** * list集合stream流式操作 */ @Test public void testStreamList() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.stream().filter(e -> e.startsWith("張")) //過濾所有姓張的人 .filter(e -> e.length() == 3) //過濾所有姓名是3個字的人 .forEach(System.out::println); //遍歷列印,System.out::println表明System.out呼叫println列印方法 } -
limit(n):獲取前n個元素
/** * limit 方法 ,回傳前n個元素資料值組成的Stream, */ @Test public void testLimit() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); list.stream().limit(3).forEach(System.out::println); //取前3個 } -
skip(n):跳過n元素,配合limit(n)可實作分頁
/** * skip方法 ,跳過前n個元素的中間流操作,回傳剩下的值, */ @Test public void testSkip() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); //list.stream().skip(3).forEach(System.out::println); //跳過前3個 list.stream().skip(3).limit(2).forEach(System.out::println); //skip+limit實作分頁 } -
distinct:通過流中元素的 hashCode() 和 equals() 去除重復元素
/** * distinct, 回傳去重的Stream */ @Test public void testDistinct() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("李四"); list.add("王五"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); list.stream().distinct().collect(Collectors.toList()).forEach(System.out::println); }
1.2 排序
-
sorted():自然排序,流中元素需實作Comparable介面
/** * sorted: 回傳一個排序的Stream */ @Test public void testSorted() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("李四"); list.add("王五"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); list.stream().distinct().sorted().collect(Collectors.toList()).forEach(System.out::println); } -
sorted(Comparator com):定制排序,自定義Comparator排序器
1.3 映射
- map:接收一個函式作為引數,該函式會被應用到每個元素上,并將其映射成一個新的元素,
/**
* 遍歷map集合,截取substring(2)開始的值
*/
@Test
public void testMap() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
Stream<String> stream = list.stream().map(e -> e.substring(2));
stream.forEach(System.out::println);
}
-
forEach:ForEach流式遍歷集合
/** * forEach, ForEach流式遍歷list集合 */ @Test public void testForEach() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.stream().forEach(System.out::println); }
2. 流的終止操作
2.1 匹配、聚合操作
-
allMatch:接收一個 Predicate 函式,當流中每個元素都符合該斷言時才回傳true,否則回傳false
/** * allMatch:接收一個 Predicate 函式,當流中每個元素都符合該斷言時才回傳true,否則回傳false */ @Test public void testAllMatch() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("李四"); list.add("王五"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); boolean b = list.stream() .allMatch(e -> list.size() > 8); System.out.println("b = " + b); } -
noneMatch:接收一個 Predicate 函式,當流中每個元素都不符合該斷言時才回傳true,否則回傳false
/** * noneMatch: 接收一個 Predicate 函式,當流中每個元素都不符合該斷言時才回傳true,否則回傳false */ @Test public void testNoneMatch() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("李四"); list.add("王五"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); boolean b = list.stream().noneMatch(e->e.equals("張三")); System.out.println("b = " + b); } -
anyMatch:接收一個 Predicate 函式,只要流中有一個元素滿足該斷言則回傳true,否則回傳false
/** * anyMatch:接收一個 Predicate 函式,只要流中有一個元素滿足該斷言則回傳true,否則回傳false */ @Test public void testAnyMatch() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("李四"); list.add("王五"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); boolean b = list.stream().anyMatch(e -> e.equals("王二麻子")); System.out.println("b = " + b); } -
findFirst:回傳流中第一個元素
/** * findFirst:回傳流中第一個元素 */ @Test public void testFindFirsth() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("李四"); list.add("王五"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); Optional<String> first = list.stream().findFirst(); System.out.println("first = " + first.get()); } -
findAny:回傳流中的任意元素
/** * findAny:回傳流中第一個元素 */ @Test public void testFindAny() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("李四"); list.add("王五"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); Optional<String> any = list.stream().findAny(); System.out.println("any = " + any.get()); } -
count:回傳流中元素的總個數
/** * count,獲取List集合的長度 */ @Test public void testCount() { List<String> list = new ArrayList<>(); list.add("張三三"); list.add("李四"); list.add("王五"); list.add("孫七"); list.add("趙八"); list.add("王二麻子"); long count = list.stream().count(); System.out.println("count = " + count); int size = list.size(); System.out.println("size = " + size); } -
max:回傳流中元素最大值
/** * max:回傳流中元素最大值 */ @Test public void testMax() { List<Integer> list = new ArrayList<>(); list.add(11); list.add(22); list.add(33); list.add(44); list.add(55); list.add(66); list.add(77); list.add(88); Integer integer = list.stream().max(Integer::compareTo).get(); System.out.println("integer = " + integer); } -
min:回傳流中元素最小值
/** * min:回傳流中元素最小值 */ @Test public void testMin() { List<Integer> list = new ArrayList<>(); list.add(11); list.add(22); list.add(33); list.add(44); list.add(55); list.add(66); list.add(77); list.add(88); Integer integer = list.stream().min(Integer::compareTo).get(); System.out.println("integer = " + integer); list.stream().limit(1).limit(2).distinct().skip(3).filter(f -> f.equals(55)).forEach(System.out::println); }
2.2 Collector 工具庫:Collectors
-
Collectors
Student s1 = new Student("aa", 10,1); Student s2 = new Student("bb", 20,2); Student s3 = new Student("cc", 10,3); List<Student> list = Arrays.asList(s1, s2, s3); //裝成list List<Integer> ageList = list.stream().map(Student::getAge).collect(Collectors.toList()); // [10, 20, 10] //轉成set Set<Integer> ageSet = list.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 10] //轉成map,注:key不能相同,否則報錯 Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10} //字串分隔符連接 String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")")); // (aa,bb,cc) //聚合操作 //1.學生總數 Long count = list.stream().collect(Collectors.counting()); // 3 //2.最大年齡 (最小的minBy同理) Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get(); // 20 //3.所有人的年齡 Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge)); // 40 //4.平均年齡 Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge)); // 13.333333333333334 // 帶上以上所有方法 DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge)); System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage()); //分組 Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge)); //多重分組,先根據型別分再根據年齡分 Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getType, Collectors.groupingBy(Student::getAge))); //磁區 //分成兩部分,一部分大于10歲,一部分小于等于10歲 Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10)); //規約 Integer allAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get(); //40
六、Stream操作代碼
為了方便小伙伴們看到這篇博客時,學習的更加輕松,這里貼出原始碼,小伙伴們學習是可貼到IDEA運行查看Stream過濾篩選的結果,以此對Stream的流式操作更加熟悉,
package com.java8.example.chapter3;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @desc: Stream流式操作
* @author: cao_wencao
* @date: 2020-09-17 15:24
*/
public class TestStreamList {
/**
* list集合stream流式操作
*/
@Test
public void testStreamList() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.stream().filter(e -> e.startsWith("張")) //過濾所有姓張的人
.filter(e -> e.length() == 3) //過濾所有姓名是3個字的人
.forEach(System.out::println); //遍歷列印,System.out::println表明System.out呼叫println列印方法
}
/**
* stream,獲取各種集合的stream流
*/
@Test
public void testCollectionStream() {
List<String> stringList = new ArrayList<>();
Set<String> stringSet = new HashSet<>();
Map<String, Object> stringObjectMap = new HashMap<>();
String[] stringArray = {"張三三", "李四", "王五", "王五", "趙八",};
//通過list獲取stream流
Stream<String> streamList = stringList.stream();
//通過set獲取stream流
Stream<String> streamSet = stringSet.stream();
//通過map獲取stream流
Stream<String> streamMap = stringObjectMap.keySet().stream();
//通過array獲取stream流
Stream<String> streamArray1 = Stream.of(stringArray);
}
/**
* forEach, ForEach流式遍歷list集合
*/
@Test
public void testForEach() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.stream().forEach(System.out::println);
}
/**
* filter 方法 , 回傳符合過濾條件的值
*/
@Test
public void testFilter() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.stream().filter(e -> e.contains("張")).forEach(System.out::println);
}
/**
* 遍歷map集合,截取substring(2)開始的值
*/
@Test
public void testMap() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
Stream<String> stream = list.stream().map(e -> e.substring(2));
stream.forEach(System.out::println);
}
/**
* count,獲取List集合的長度
*/
@Test
public void testCount() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
long count = list.stream().count();
System.out.println("count = " + count);
int size = list.size();
System.out.println("size = " + size);
}
/**
* limit 方法 ,回傳前n個元素資料值組成的Stream,
*/
@Test
public void testLimit() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
list.stream().limit(3).forEach(System.out::println); //取前3個
}
/**
* skip方法 ,跳過前n個元素的中間流操作,回傳剩下的值,
*/
@Test
public void testSkip() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
//list.stream().skip(3).forEach(System.out::println); //跳過前3個
list.stream().skip(3).limit(2).forEach(System.out::println); //skip+limit實作分頁
}
/**
* collect,將流轉化為集合
*/
@Test
public void testCollect() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
List<String> collect = list.stream().skip(3).limit(2).collect(Collectors.toList());
collect.forEach(System.out::println);
}
/**
* distinct, 回傳去重的Stream
*/
@Test
public void testDistinct() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("李四");
list.add("王五");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
list.stream().distinct().collect(Collectors.toList()).forEach(System.out::println);
}
/**
* sorted: 回傳一個排序的Stream
*/
@Test
public void testSorted() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("李四");
list.add("王五");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
list.stream().distinct().sorted().collect(Collectors.toList()).forEach(System.out::println);
}
/**
* anyMatch:接收一個 Predicate 函式,只要流中有一個元素滿足該斷言則回傳true,否則回傳false
*/
@Test
public void testAnyMatch() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("李四");
list.add("王五");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
boolean b = list.stream().anyMatch(e -> e.equals("王二麻子"));
System.out.println("b = " + b);
}
/**
* noneMatch: 接收一個 Predicate 函式,當流中每個元素都不符合該斷言時才回傳true,否則回傳false
*/
@Test
public void testNoneMatch() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("李四");
list.add("王五");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
boolean b = list.stream().noneMatch(e->e.equals("張三"));
System.out.println("b = " + b);
}
/**
* allMatch:接收一個 Predicate 函式,當流中每個元素都符合該斷言時才回傳true,否則回傳false
*/
@Test
public void testAllMatch() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("李四");
list.add("王五");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
boolean b = list.stream()
.allMatch(e -> list.size() > 8);
System.out.println("b = " + b);
}
/**
* findFirst:回傳流中第一個元素
*/
@Test
public void testFindFirsth() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("李四");
list.add("王五");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
Optional<String> first = list.stream().findFirst();
System.out.println("first = " + first.get());
}
/**
* findAny:回傳流中第一個元素
*/
@Test
public void testFindAny() {
List<String> list = new ArrayList<>();
list.add("張三三");
list.add("李四");
list.add("李四");
list.add("王五");
list.add("王五");
list.add("孫七");
list.add("趙八");
list.add("王二麻子");
Optional<String> any = list.stream().findAny();
System.out.println("any = " + any.get());
}
/**
* max:回傳流中元素最大值
*/
@Test
public void testMax() {
List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
list.add(44);
list.add(55);
list.add(66);
list.add(77);
list.add(88);
Integer integer = list.stream().max(Integer::compareTo).get();
System.out.println("integer = " + integer);
}
/**
* min:回傳流中元素最小值
*/
@Test
public void testMin() {
List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
list.add(44);
list.add(55);
list.add(66);
list.add(77);
list.add(88);
Integer integer = list.stream().min(Integer::compareTo).get();
System.out.println("integer = " + integer);
list.stream().limit(1).limit(2).distinct().skip(3).filter(f -> f.equals(55)).forEach(System.out::println);
}
}
總結
以上就是對于JDK1.8中Stream流式計算的一個簡單介紹,關于JDK1.8中的新特性,用法遠遠不止這些,這篇文章只作為一個簡單的入門使用,更深層次的理解請后續關注JDK1.8系列的文章,
CSDN認證博客專家
Java
Spring Boot
架構
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/131730.html
標籤:AI
