Stream API
什麼是Stream
Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查找、過濾和映射數據等操作。
使用Stream API 對集合數據進行操作,就類似於使用 SQL 執行的資料庫查詢。也可以使用 Stream API 來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用的處理數據的方式。
流(Stream )是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。
「集合講的是數據,流講的是計算! 」
注意:
1. Stream 自己不會存儲元素。
2. Stream 不會改變源對象。相反,他們會返回一個持有結果的新Stream。
3. Stream 操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。
Stream操作的三個步驟
1. 創建Stream
一個數據源(如:集合、數組),獲取一個流
2. 中間操作
一個中間操作鏈,對數據源的數據進行處理
3. 終止操作( 終端操作)
一個終止操作,執行中間操作鏈,併產生結果
創建Stream的四種方式
1. 通過 Collection 系列集合提供的 stream() 創建一個順序流,或 parallelStream() 創建一個並行流。
2. 通過 Arrays 中的靜態方法 stream() 獲取數組流。
3. 通過 Stream 類中的靜態方法 of() 創建。
4. 通過函數創建無限流。
4.1 迭代
4.2 生成
示例:


/** * 創建 Stream */ @Test public void test1(){ //1. 通過 Collection 系列集合提供的 stream() 創建一個順序流,或 parallelStream() 創建一個並行流。 List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream(); Stream<String> stream2 = list.parallelStream(); //2. 通過 Arrays 中的靜態方法 stream() 獲取數組流。 Stream<String> stream3 = Arrays.stream(new String[10]); Stream<Integer> stream4 = Arrays.stream(new Integer[]{1, 2}); Stream<Employee> stream5 = Arrays.stream(new Employee[1]); //... //3. 通過 Stream 類中的靜態方法 of() 創建。 Stream<String> stream6 = Stream.of("a", "b", "c"); //4. 通過函數創建無限流。 //4.1 迭代 Stream<Integer> stream7 = Stream.iterate(0, (x) -> x + 2); stream7.limit(10).forEach(System.out::println); //4.2 生成 Stream<UUID> stream8 = Stream.generate(() -> UUID.randomUUID()); stream8.limit(10).forEach(System.out::println); }
View Code
Stream的中間操作
多個 中間操作 可以連接起來形成一個 流水線,除非流水線上觸發終止操作,否則 中間操作不會執行任何的 處理!而在 終止操作時一次性全部 處理,稱為「惰性求值」。
1. 篩選與切片
方法 | 描述 |
filter(Predicate p) | 接收Lambda,從流中排出某些元素。 |
distinct() | 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素 |
limit(long maxSize) | 截斷流,使其元素不超過給定數量 |
skip(long n) | 跳過元素,返回一個扔掉了前 n 個元素的流,若流中元素不足n個,則返回一個空流。與limit(n)互補 |
示例:


public class Employee { private String name; private Integer age; private Integer gender; private Double salary; } /////////////////////////////////////////////////////////////////////////////// List<Employee> emps = Arrays.asList( new Employee("李四", 59,1, 6666.66), new Employee("張三", 18,1, 9999.99), new Employee("王五", 28,1, 3333.33), new Employee("趙六", 8, 0,7777.77), new Employee("趙六", 8, 0,7777.77), new Employee("趙六", 8, 0,7777.77), new Employee("田七", 38,0, 5555.55) ); /** * 對 Stream 進行流水線式的中間操作 */ @Test public void test2(){ /* 篩選與切片 filter——接收 Lambda , 從流中排除某些元素。 limit——截斷流,使其元素不超過給定數量。 skip(n) —— 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補 distinct——篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素 */ //只有當做終止操作時,所有的中間操作會一次性的全部執行,稱為「惰性求值」 emps.stream().filter((e) -> e.getAge() > 20).forEach(System.out::println); System.out.println("---------------------------------------"); emps.stream().filter((e) -> e.getSalary() > 5000).limit(2).forEach(System.out::println); System.out.println("---------------------------------------"); emps.stream().filter((e) -> e.getSalary() > 5000).skip(2).forEach(System.out::println); System.out.println("---------------------------------------"); emps.stream().distinct().forEach(System.out::println);//這裡需要重寫Employee對象的hashCode() 和 equals()方法才能去重成功 }
View Code
2. 映射
方法 | 描述 |
map(Function f) | 接收一個函數作為參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 LongStream。 |
flatMap(Function f) | 接收一個函數作為參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流。 |
示例:


/** * 映射: map ——接收 Lambda , 將元素轉換成其他形式或提取資訊。接收一個函數作為參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。 */ @Test public void test3(){ emps.stream() .map(Employee::getName).forEach(System.out::println); List<String> names = emps.stream().map(Employee::getName).collect(Collectors.toList());//提取emps 集合中的所有姓名,構成一個新的集合 System.out.println(names); System.out.println("-------------------------------------------"); List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee"); strList.stream().map(String::toUpperCase).forEach(System.out::println); } /** * 映射: flatMap ——接收一個函數作為參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流 */ @Test public void test4(){ List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee"); Stream<Stream<Character>> stream2 = strList.stream() .map(TestStream1::filterCharacter);// map方法返回一個流,map中執行的方法本身又返回一個流,所以返回值就是 Stream<Stream<Character>> 結構 stream2.forEach((sm) -> { sm.forEach(System.out::println); }); System.out.println("---------------------------------------------"); Stream<Character> stream3 = strList.stream() .flatMap(TestStream1::filterCharacter);//flatMap 把所有的流連接成一個流 stream3.forEach(System.out::println); } /** * 處理字元串,截取成每個字元構成的一個集合,返回這個字符集合的流 * @param str * @return */ public static Stream<Character> filterCharacter(String str){ List<Character> list = new ArrayList<>(); for (Character ch : str.toCharArray()) { list.add(ch); } return list.stream(); } /** * 映射: mapToDouble,mapToInt,mapToLong */ @Test public void test5(){ //接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 DoubleStream。 emps.stream().mapToDouble(Employee::getSalary).forEach(System.out::println); //接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 IntStream。 emps.stream().mapToInt(Employee::getAge).forEach(System.out::println); //接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 LongStream。 List<Long> longList = Arrays.asList(666L,777L,888L); longList.stream().mapToLong(Long::longValue).forEach(System.out::println); }
View Code
3. 排序
方法 | 描述 |
sorted() | 產生一個新流,其中按自然順序排序。 |
sorted(Comparator comp) | 產生一個新流,其中按比較器順序排序。 |
示例:


/** * 排序: * sorted()——自然排序 * sorted(Comparator com)——訂製排序 */ @Test public void test6(){ emps.stream() .map(Employee::getSalary) .sorted() .forEach(System.out::println); System.out.println("------------------------------------"); emps.stream() .sorted((x, y) -> { if(x.getAge() == y.getAge()){ return x.getName().compareTo(y.getName()); }else{ return Integer.compare(x.getAge(), y.getAge()); } }).forEach(System.out::println); }
View Code
Stream的終止操作
終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
1. 查找與匹配
方法 | 描述 |
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count() | 返迴流中元素總數 |
max(Comparator c) | 返迴流中最大值 |
min(Comparator c) | 返迴流中最小值 |
forEach(Consumer c) | 內部迭代(使用Collection介面需要用戶去做迭代,稱為外部迭代。相反,StreamAPI使用內部迭代,幫你把迭代做了) |
示例:


public class Employee { private String name; private Integer age; private Integer gender; private Double salary; private Status status; public enum Status { FREE, BUSY, VOCATION; } } //////////////////////////////////////////////////////////////////////////////// import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.Optional; public class TestStream2 { List<Employee> emps = Arrays.asList( new Employee("李四", 59,1, 6666.66, Employee.Status.VOCATION), new Employee("張三", 18,1, 9999.99, Employee.Status.BUSY), new Employee("王五", 28,1, 3333.33, Employee.Status.FREE), new Employee("趙六", 8, 0,7777.77, Employee.Status.BUSY), new Employee("趙六", 8, 0,7777.77, Employee.Status.FREE), new Employee("趙六", 8, 0,7777.77, Employee.Status.BUSY), new Employee("田七", 38,0, 5555.55, Employee.Status.VOCATION) ); /** * 終止操作 * allMatch——檢查是否匹配所有元素 * anyMatch——檢查是否至少匹配一個元素 * noneMatch——檢查是否沒有匹配的元素 */ @Test public void test1(){ boolean bl = emps.stream() .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));//是否所有員工的狀態都是 BUSY System.out.println(bl); boolean bl1 = emps.stream() .anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); //是否至少一個員工狀態是 BUSY System.out.println(bl1); boolean bl2 = emps.stream() .noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); //是否沒有員工的狀態是 BUSY System.out.println(bl2); } /** * 終止操作 * findFirst——返回第一個元素 * findAny——返回當前流中的任意元素 */ @Test public void test2(){ Optional<Employee> op = emps.stream() .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())) .findFirst(); System.out.println(op.get()); System.out.println("--------------------------------"); Optional<Employee> op2 = emps.parallelStream() .filter((e) -> e.getStatus().equals(Employee.Status.FREE)) .findAny(); System.out.println(op2.get()); } /** * 終止操作 * count——返迴流中元素的總個數 * max——返迴流中最大值 * min——返迴流中最小值 */ @Test public void test3(){ long count = emps.stream() .filter((e) -> e.getStatus().equals(Employee.Status.FREE)) .count(); System.out.println(count); Optional<Double> op = emps.stream() .map(Employee::getSalary) .max(Double::compare); System.out.println(op.get()); Optional<Employee> op2 = emps.stream() .min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())); System.out.println(op2.get()); } }
View Code
2. 歸約
方法 | 描述 |
reduce(T iden,BinaryOperator b) | 可以將流中元素反覆結合起來,得到一個值。返回T |
reduce(BinaryOperator b) | 可以將流中元素反覆結合起來,得到一個值。返回 Optional<T> |
示例:


/** * 歸約 * reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以將流中元素反覆結合起來,得到一個值。 */ @Test public void test1(){ List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer sum = list.stream() .reduce(0, (x, y) -> x + y); //初始值為0,x 為上一次運算的結果,y 為當次傳入的list集合中每個下標的值,然後得到一個最終的值 /* 第一次運算:初始值為 0 ,沒有上一次運算的結果,所以 x = 0,y = list.get(0) => 1 , x + y 值為 1 第二次運算:初始值為上一次運算的結果 1 ,所以 x = 1,y = list.get(1) => 2 , x + y 值為 3 第三次運算:初始值為上一次運算的結果 3 ,所以 x = 3,y = list.get(2) => 3, x + y 值為 6 第四次運算:初始值為上一次運算的結果 6 ,所以 x = 6,y = list.get(3) => 4, x + y 值為 10 ....... 直到將 list 中的值全部拿來運算後,歸約結束 */ System.out.println(sum); System.out.println("----------------------------------------"); Optional<Double> op = emps.stream() .map(Employee::getSalary) .reduce(Double::sum); //這個道理同上,唯一不同的是,上面的歸約結束返回的直接是一個運算結果,這裡返回的是Optional對象 //因為上面有初始值,那麼運算後的結果一定不會為Null,下面的沒有初始值,運算後的結果可能為Null,所以對於這種結果可能為Null的,就默認封裝到Optional中去,就避免空指針 System.out.println(op.get()); }
View Code
備註:map 和 reduce 的連接通常稱為 map-reduce 模式,比較常用。
3. 收集
方法 | 描述 |
collect(Collector c) | 將流轉換成其他形式。接收一個Collector介面的實現,用於給Stream中元素做匯總的方法 |
示例:


/** * 收集 * collect——將流轉換為其他形式。接收一個 Collector介面的實現,用於給Stream中元素做匯總的方法 */ @Test public void test2(){ List<String> list = emps.stream() .map(Employee::getName) .collect(Collectors.toList());//把emps集合中的所有員工姓名收集到list中 list.forEach(System.out::println); System.out.println("----------------------------------"); Set<String> set = emps.stream() .map(Employee::getName) .collect(Collectors.toSet());//把emps集合中的所有員工姓名收集到set中 set.forEach(System.out::println); System.out.println("----------------------------------"); HashSet<String> hs = emps.stream() .map(Employee::getName) .collect(Collectors.toCollection(HashSet::new));//把emps集合中的所有員工姓名收集到HashSet中 hs.forEach(System.out::println); } /** * 收集 * collect——將流轉換為其他形式。接收一個 Collector介面的實現,用於給Stream中元素做匯總的方法 */ @Test public void test3(){ Optional<Double> max = emps.stream() .map(Employee::getSalary)//平均值 .collect(Collectors.maxBy(Double::compare)); System.out.println(max.get()); Optional<Employee> op = emps.stream() .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));//最小值 System.out.println(op.get()); Double sum = emps.stream() .collect(Collectors.summingDouble(Employee::getSalary));//和 System.out.println(sum); Double avg = emps.stream() .collect(Collectors.averagingDouble(Employee::getSalary));//平均值 System.out.println(avg); Long count = emps.stream() .collect(Collectors.counting());//總數 System.out.println(count); System.out.println("--------------------------------------------"); DoubleSummaryStatistics dss = emps.stream() .collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.println(dss.getMax());//獲取最大值 System.out.println(dss.getAverage());//平均值 System.out.println(dss.getMin());//最小值 System.out.println(dss.getCount());//總數 System.out.println(dss.getSum());//和 } /** * 收集 * collect——將流轉換為其他形式。接收一個 Collector介面的實現,用於給Stream中元素做匯總的方法 * 分組、多級分組 */ @Test public void test4(){ //分組 Map<Employee.Status, List<Employee>> map = emps.stream() .collect(Collectors.groupingBy(Employee::getStatus));//按照狀態分組 System.out.println(map); System.out.println("--------------------------------------------"); //多級分組 Map<Employee.Status, Map<String, List<Employee>>> map1 = emps.stream() .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> { if(e.getAge() >= 60) return "老年"; else if(e.getAge() >= 35) return "中年"; else return "成年"; })));//Collectors.groupingBy 中第二個參數接收的還是 Collectors.groupingBy,所以可以一層一層分組 System.out.println(map1); } /** * 收集 * collect——將流轉換為其他形式。接收一個 Collector介面的實現,用於給Stream中元素做匯總的方法 * 分區 */ @Test public void test5(){ Map<Boolean, List<Employee>> map = emps.stream() .collect(Collectors.partitioningBy((e) -> e.getSalary() >= 5000));//按照工資5000分區 System.out.println(map); } /** * 收集 * collect——將流轉換為其他形式。接收一個 Collector介面的實現,用於給Stream中元素做匯總的方法 * 連接 */ @Test public void test6(){ String str = emps.stream() .map(Employee::getName) //.collect(Collectors.joining());//直接連接 //.collect(Collectors.joining("," ));//所有值連接中間用 , 隔開 .collect(Collectors.joining("," , "----", "===="));//所有值連接中間用 , 隔開;連接後的字元串添加前綴:"----"、後綴:"====" System.out.println(str); }
View Code
Collector 介面中方法的實現決定了如何對流執行收集操作(如收集到 List、Set、Map)。但是 Collectors 實用類提供了很多靜態方法,可以方便地創建常見收集器實例,具體方法與實例如下表:
注意:流進行了終止操作後,不能再次使用