Java8新特性之Stream API
1、Stream API的基本介紹
Java 8 API添加了一個新的抽象稱為流 Stream,可以讓你以一種聲明的方式處理數據。Stream API可以極大提高Java程序員的生產力,讓程序員寫出高效率、干凈、簡潔的代碼。
Stream 是Java8中處理集合的關鍵抽象概念,它可以對集合進行非常復雜的查找、過濾、篩選等操作。這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。元素流在管道中經過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結果。
1.1、什么是stream(流)
流(Stream)是數據渠道,用于操作數據源(集合、數組等)所生成的元素序列。集合講的是數據,流講的是計算。
注意:
- Stream是無存儲的。stream不是一種數據結構,它只是某種數據源的一個視圖,數據源可以是一個數組,Java容器或I/O channel等。
- Stream不會改變源對象。相反,他會返回一個持有結果的新Stream。對stream的任何修改都不會修改背后的數據源,比如對stream執行過濾操作并不會刪除被過濾的元素,而是會產生一個不包含被過濾元素的新stream。
- 惰式執行。stream上的操作并不會立即執行,只有等到用戶真正需要結果的時候才會執行。
- 可消費性。stream只能被“消費”一次,一旦遍歷過就會失效,就像容器的迭代器那樣,想要再次遍歷必須重新生成。
2、Stream的操作步驟
Stream有如下三個操作步驟:
- 創建Stream:從一個數據源,如集合、數組中獲取流。
- 中間操作:一個操作的中間鏈,對數據源的數據進行操作。
- 終止操作:一個終止操作,執行中間操作鏈,并產生結果。
操作步驟如下圖:

要注意的是,對流的操作完成后需要進行關閉操作(或者用JAVA7的try-with-resources)。
3、創建 stream
在 Java 8 中, 集合接口有兩個方法來生成流:
-
stream() ? 為集合創建串行流。
-
parallelStream() ? 為集合創建并行流。
4、中間操作
多個中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何處理,而在終止操作時一次性全部處理,稱為“惰性求值”。
常見的中間操作有以下幾種:
- 篩選與切片
- 映射
- 排序
下面的所有示例中基于的類和變量如下:
public class Person { private String name; private Integer age; private String country; private char sex; public Person(String name, Integer age, String country, char sex) { this.name = name; this.age = age; this.country = country; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", country='" + country + '\'' + ", sex=" + sex + '}'; } }
personList 集合變量:
List<Person> personList = new ArrayList<>(); personList.add(new Person("歐陽雪",18,"中國",'F')); personList.add(new Person("Tom",24,"美國",'M')); personList.add(new Person("Harley",22,"英國",'F')); personList.add(new Person("向天笑",20,"中國",'M')); personList.add(new Person("李康",22,"中國",'M')); personList.add(new Person("小梅",20,"中國",'F')); personList.add(new Person("何雪",21,"中國",'F')); personList.add(new Person("李康",22,"中國",'M'));
4.1、篩選與切片
方法如下:
| 方法 | 描述 |
|---|---|
| filter(Predicate p) | 接收Lambda,從流中排除某些元素 |
| limit(long maxSize) | 截斷流,使其元素不超過給定對象 |
| skip(long n) | 跳過元素,返回一個扔掉了前n個元素的流,若流中元素不足n個,則返回一個空流,與limit(n)互補 |
| distinct() | 篩選,去除重復元素。會通過流所生成元素的hashcode()和equals()方法來判斷是否為同一元素,所以要想去除重復,可能需要覆寫類中這兩個方法。 |
示例:
- filter 和 Limit 使用示例:
personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println); //從Person列表中取出兩個女性
輸出結果:
Person{name='歐陽雪', age=18, country='中國', sex=F}
Person{name='Harley', age=22, country='英國', sex=F}
- skip 使用示例:
personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println); //先從Person列表中過濾出女性列表,然后排除第一個元素
輸出結果:
Person{name='Harley', age=22, country='英國', sex=F}
Person{name='小梅', age=20, country='中國', sex=F}
Person{name='何雪', age=21, country='中國', sex=F}
- distinct 使用示例:
personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
如果我們沒有覆寫 person 類中的 equals() 或者 hashcode() 方法,則判斷是否為同一元素的依據可能無法照我們所想的那樣,最終輸出結果會如下:
Person{name='Tom', age=24, country='美國', sex=M}
Person{name='向天笑', age=20, country='中國', sex=M}
Person{name='李康', age=22, country='中國', sex=M}
Person{name='李康', age=22, country='中國', sex=M}
可以發現,同一元素 "李康" 并沒有被去除。我們可以在 person 類中覆寫 equals() 方法:
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return sex == person.sex && Objects.equals(name, person.name) && Objects.equals(age, person.age) && Objects.equals(country, person.country); }
然后再執行 distinct 示例,輸出結果如下:
Person{name='Tom', age=24, country='美國', sex=M}
Person{name='向天笑', age=20, country='中國', sex=M}
Person{name='李康', age=22, country='中國', sex=M}
可以看到正常去除了重復元素。
4.2、映射
| 方法 | 描述 |
|---|---|
| map(Function f) | 接收一個函數作為參數,該函數會被應用到每個元素上,并將其映射成一個新的元素并返回 |
| mapToDouble(Function f) | 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的DoubleStream并返回 |
| mapToInt(Function f) | 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的IntStream并返回 |
| mapToLong(Function f) | 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的LongStream并返回 |
| flatMap(Function f) |
接收一個函數作為參數,將流中的每個值都換成另一個流,然后將所有流連接成一個流 |
map在接收到流后,直接將Stream放入到一個Stream中,最終整體返回一個包含了多個Stream的Stream。flatMap 和 map 等映射方法不一樣,flatMap 在接收到Stream后,會將接收到的Stream中的每個元素取出來放入一個Stream中,最后一次性將一個包含多個元素的Stream返回。所以說,flatMap 最后輸出的可能會是一個元素,而不是像 map 是多個元素。
map 使用示例:
personList.stream().map(n -> n.getName()).forEach(n -> System.out.println(n)); //將輸出 personlist 所有元素的 name 屬性
mapToInt 使用示例:
personList.stream().mapToInt(n -> n.getAge()).forEach(n -> System.out.println(n)); //將輸出personlist所有元素的age屬性
4.3、排序

5、終止操作
5.1、查找與匹配
| 方法 | 描述 |
|---|---|
| allMatch(Predicate p) | 檢查是否匹配所有元素,返回一個Boolean值 |
| anyMatch(Predicate p) | 檢查是否至少匹配一個元素,返回一個Boolean值 |
| noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
| findFirst() | 返回第一個元素 |
| findAny() | 返回當前流中的任意元素 |
| count() | 返回流中元素總數 |
| max(Comparator c) | 返回流中最大值 |
| min(Comparator c) | 返回流中最小值 |
| forEach(Consumer c) | 內部迭代(使用 Collection 接口需要用戶去做迭代,稱為外部迭代。相反, Stream API 使用內部迭代) |
allMatch、anyMatch 使用示例:
boolean flage = personList.stream().allMatch(p -> p.getAge() >= 21); //false boolean flage2 = personList.stream().anyMatch(p -> p.getAge() >= 21); //true
max、min使用示例:
Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge())); System.out.println("年齡最大的人信息:" + maxAge.get()); //輸出:年齡最大的人信息:Person{name='Tom', age=24, country='美國', sex=M} Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge())); System.out.println("年齡最小的人信息:" + minAge.get()); //輸出:年齡最小的人信息:Person{name='歐陽雪', age=18, country='中國', sex=F}
5.2、歸納

5.3、收集

Collector 接口中方法的實現決定了如何對流執行收集操作(如收集到List、Set、Map)。
Collector 實用類提供了很多靜態方法可以方便的收集:


Collectors.toList 使用示例:
final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList()); System.out.println(collect); //輸出:[中國, 美國, 英國]
Collectors.averagingInt 使用示例:
final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge())); System.out.println("平均年齡為:" + collect1); //輸出:平均年齡為:21.125
final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo)); System.out.println(maxAge2.get()); //輸出:24
Collectors.groupingBy 方法接收一個函數,在該函數的函數體里指定根據什么來進行分組。該方法返回一個map,key 為指定的用于分組的屬性中各個分組的值,value為 list,包含各個分組的元素。
使用示例:
Map<String, List<Person>> map= personList.stream().collect(Collectors.groupingBy(p -> p.getCountry()));
System.out.println(map);
輸出結果:
{ 美國=[Person{name='Tom', age=24, country='美國', sex=M}], 中國=[ Person{name='歐陽雪', age=18, country='中國', sex=F}, Person{name='向天笑', age=20, country='中國', sex=M}, Person{name='李康', age=22, country='中國', sex=M}, Person{name='小梅', age=20, country='中國', sex=F}, Person{name='何雪', age=21, country='中國', sex=F}, Person{name='李康', age=22, country='中國', sex=M} ], 英國=[Person{name='Harley', age=22, country='英國', sex=F}] }

浙公網安備 33010602011771號