Java8 Stream学习笔记

一、什么是Stream流(WHAT)

在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增、删、改、查、聚合、统计、过滤等操作。相比之下,关系型数据库中也同样有这些操作,但是在Java 8之前,集合和数组的处理并不是很便捷。

不过,这一问题在Java 8中得到了改善,Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream是Java 8新增的接口,Stream可以认为是一个高级版本的 Iterator。它代表着数据流,流中的数据元素的数量可以是有限的,也可以是无限的。

Stream作为java8的新特性,基于lambda表达式,是对集合对象功能的增强,它专注于对集合对象进行各种高效、便利的聚合操作或者大批量的数据操作,提高了编程效率和代码可读性。如果在项目中经常用到集合,遍历集合可以试下lambda表达式,经常还要对集合进行过滤和排序,Stream就派上用场了。

本文就来介绍下如何使用Stream。特别说明一下,关于Stream的性能及原理不是本文的重点,这里简单提一下:

Stream的原理:将要处理的元素看做一种流,流在管道中传输,并且可以在管道的节点上处理,包括过滤筛选、去重、排序、聚合等。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。

Stream有以下特性及优点:

无存储。Stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。

为函数式编程而生。对Stream的任何修改都不会修改背后的数据源,比如对Stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新Stream。

惰式执行。Stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。

可消费性。Stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

我们举一个例子,来看一下到底Stream可以做什么事情:

上面的例子中,获取一些带颜色塑料球作为数据源,首先过滤掉红色的、把它们融化成随机的三角形。再过滤器并删除小的三角形。最后计算出剩余图形的周长。

如上图,对于流的处理,主要有三种关键性操作:分别是 流的创建 中间操作 (intermediate operation)以及 最终操作 (terminal operation)。

二、为什么要使用Stream流(WHY)

那我们为什么要使用Stream流呢?什么场景下会使用到它呢?不会用它会死么?我先不试着去回答这些问题,我们来直接看一下Demo,这个Demo展示的是一些常见的场景下,原始的写法以及使用Stream流之后的写法,相信看完这个Demo之后大家对于上述的问题心中都会有自己的答案了,OK,show me the code:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
public class Java8Tester {
    public static void main(String args[]){
        System.out.println("使用 Java 7: ");
        // 计算空字符串
        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
        System.out.println("列表: " +strings);
        long count = getCountEmptyStringUsingJava7(strings);
        System.out.println("空字符数量为: " + count);
        count = getCountLength3UsingJava7(strings);
        System.out.println("字符串长度为 3 的数量为: " + count);
        // 删除空字符串
        List<String> filtered = deleteEmptyStringsUsingJava7(strings);
        System.out.println("筛选后的列表: " + filtered);
        // 删除空字符串,并使用逗号把它们合并起来
        String mergedString = getMergedStringUsingJava7(strings,", ");
        System.out.println("合并字符串: " + mergedString);
        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        // 获取列表元素平方数
        List<Integer> squaresList = getSquares(numbers);
        System.out.println("平方数列表: " + squaresList);
        List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
        System.out.println("列表: " +integers);
        System.out.println("列表中最大的数 : " + getMax(integers));
        System.out.println("列表中最小的数 : " + getMin(integers));
        System.out.println("所有数之和 : " + getSum(integers));
        System.out.println("平均数 : " + getAverage(integers));
        System.out.println("随机数: ");
        // 输出10个随机数
        Random random = new Random();
        for(int i=0; i < 10; i++){
            System.out.println(random.nextInt());
        System.out.println("使用 Java 8: ");
        System.out.println("列表: " +strings);
        count = strings.stream().filter(string->string.isEmpty()).count();
        System.out.println("空字符串数量为: " + count);
        count = strings.stream().filter(string -> string.length() == 3).count();
        System.out.println("字符串长度为 3 的数量为: " + count);
        filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
        System.out.println("筛选后的列表: " + filtered);
        mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));
        System.out.println("合并字符串: " + mergedString);
        squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());
        System.out.println("Squares List: " + squaresList);
        System.out.println("列表: " +integers);
        IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
        System.out.println("列表中最大的数 : " + stats.getMax());
        System.out.println("列表中最小的数 : " + stats.getMin());
        System.out.println("所有数之和 : " + stats.getSum());
        System.out.println("平均数 : " + stats.getAverage());
        System.out.println("随机数: ");
        random.ints().limit(10).sorted().forEach(System.out::println);
        // 并行处理
        count = strings.parallelStream().filter(string -> string.isEmpty()).count();
        System.out.println("空字符串的数量为: " + count);
    private static int getCountEmptyStringUsingJava7(List<String> strings){
        int count = 0;
        for(String string: strings){
            if(string.isEmpty()){
                count++;
        return count;
    private static int getCountLength3UsingJava7(List<String> strings){
        int count = 0;
        for(String string: strings){
            if(string.length() == 3){
                count++;
        return count;
    private static List<String> deleteEmptyStringsUsingJava7(List<String> strings){
        List<String> filteredList = new ArrayList<String>();
        for(String string: strings){
            if(!string.isEmpty()){
                filteredList.add(string);
        return filteredList;
    private static String getMergedStringUsingJava7(List<String> strings, String separator){
        StringBuilder stringBuilder = new StringBuilder();
        for(String string: strings){
            if(!string.isEmpty()){
                stringBuilder.append(string);
                stringBuilder.append(separator);
        String mergedString = stringBuilder.toString();
        return mergedString.substring(0, mergedString.length()-2);
    private static List<Integer> getSquares(List<Integer> numbers){
        List<Integer> squaresList = new ArrayList<Integer>();
        for(Integer number: numbers){
            Integer square = new Integer(number.intValue() * number.intValue());
            if(!squaresList.contains(square)){
                squaresList.add(square);
        return squaresList;
    private static int getMax(List<Integer> numbers){
        int max = numbers.get(0);
        for(int i=1;i < numbers.size();i++){
            Integer number = numbers.get(i);
            if(number.intValue() > max){
                max = number.intValue();
        return max;
    private static int getMin(List<Integer> numbers){
        int min = numbers.get(0);
        for(int i=1;i < numbers.size();i++){
            Integer number = numbers.get(i);
            if(number.intValue() < min){
                min = number.intValue();
        return min;
    private static int getSum(List numbers){
        int sum = (int)(numbers.get(0));
        for(int i=1;i < numbers.size();i++){
            sum += (int)numbers.get(i);
        return sum;
    private static int getAverage(List<Integer> numbers){
        return getSum(numbers) / numbers.size();

个人认为使用Stream的优势:

  • 充分JDK库提供现有的API,代码写起来简洁优化,且减少逻辑错误的可能性
  • 方便实现并发。在多核情况下,可以使用并行Stream API来发挥多核优势。在单核的情况下,我们自己写的for性能不比Stream API 差多少。
  • 个人认为使用Stream的劣势:

  • 调试的难度增大,但是可以通过打thread级别的断点来实现调试
  • 总之,综合比较,推荐在做批量数据集处理的时候,在项目JDK版本允许的情况下(如果JDK版本小于8,先升级一下版本到8),尽量地去使用Java 8 Stream的特性,你的代码质量会有一个比较高的level的提升。

    三、如何使用Stream流?(HOW)
    使用Stream流分为三步:

  • 创建Stream流
  • 通过Stream流对象执行中间操作
  • 执行最终操作,得到结果
  • 通过集合的stream()方法为集合创建串行流或者parallelStream()为集合创建并行流
  • 使用流的静态方法,比如Stream.of(Object[]), IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator)。
  • 通过Arrays.stream(Object[])方法。
  • BufferedReader.lines()从文件中获得行的流。
  • Files类的操作路径的方法,如list、find、walk等。
  • 随机数流Random.ints()。
  • 其它一些类提供了创建流的方法,如BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), 和 JarFile.stream()。
  • 最底层都是依赖底层的StreamSupport类来完成Stream创建。

  • 通过已有的集合来创建流
  • List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
    Stream<String> stream = strings.stream();
    

    通过一个已有的List创建一个流,这种通过集合创建出一个Stream的方式也是比较常用的一种方式。

  • 通过Stream创建流
    可以使用Stream类提供的方法,直接返回一个由指定元素组成的流。
  • Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
    

    下面我们列举一些常见的创建流方式的Demo:

    public class StreamTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            // 从集合创建,stream()返回的是串行流,parallelStream()返回的是并行流
            Stream<String> stream = list.stream();
            Stream<String> stream1 = list.parallelStream();
            // 从数组创建
            IntStream stream2 = Arrays.stream(new int[]{1, 2, 3});
            // 创建数字流
            IntStream intStream = IntStream.of(1, 2, 3);
            // 使用random创建包含3个随机数的流
            IntStream randomStream = new Random().ints().limit(3);
    

    Stream有很多中间操作,多个中间操作可以连接起来形成一个流水线,每一个中间操作就像流水线上的一个工人,每人工人都可以对流进行加工,加工后得到的结果还是一个流。

    以下代码片段使用 filter 方法过滤掉空字符串:

    List<String> strings = Arrays.asList("A", "", "B", "C", "D");
    strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);
    //A, ,B, C, D
    

    map 方法用于映射每个元素到对应的结果。

    以下代码片段使用 map 输出了元素对应的平方数:

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    numbers.stream().map( i -> i*i).forEach(System.out::println);
    //9,4,4,9,49,9,25
    

    limit/skip

    limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。

    以下代码片段使用 limit 方法保理4个元素:

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    numbers.stream().limit(4).forEach(System.out::println);
    //3,2,2,3
    

    sorted

    sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序:

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    numbers.stream().sorted().forEach(System.out::println);
    //2,2,3,3,3,5,7
    

    distinct

    distinct主要用来去重,以下代码片段使用 distinct 对元素进行去重:

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    numbers.stream().distinct().forEach(System.out::println);
    //3,2,7,5
    

    flatMap

    flatMap用于流转换,将一个流中的每个值都转换为另一个流

    List<String> wordList = Arrays.asList("Hello", "World");
    List<String> strList = wordList.stream()
            .map(w -> w.split(" "))
            .flatMap(Arrays::stream)
            .distinct()
            .collect(Collectors.toList());
    

    map(w -> w.split(" "))的返回值为Stream<String[]>,我们想获取Stream<String>,可以通过flatMap方法完成Stream ->Stream的转换

    提供了三种匹配方式

    1.allMatch匹配所有

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().allMatch(i -> i > 3)) {
        System.out.println("值都大于3");
    

    2.anyMatch匹配其中一个

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().anyMatch(i -> i > 3)) {
        System.out.println("存在大于3的值");
    

    3. noneMatch全部不匹配

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().noneMatch(i -> i > 3)) {
        System.out.println("值都小于3");
    

    接下来我们通过一个例子和一张图,来演示下,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会发生什么。

    List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
    Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3)
                .distinct();
    

    过程及每一步得到的结果如下图:

    一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。终端操作的执行,才会真正开始流的遍历。

    Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)

    下图总结了一些常用的最终操作:

    forEach

    Stream 提供了方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
    

    count

    count用来统计流中的元素个数。

    List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
    System.out.println(strings.stream().count());
    

    collect

    collect就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果:

    List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
    strings  = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList());
    System.out.println(strings);
    //Hollis, HollisChuang, Hollis666, Hollis
    

    couting

    最后一种统计元素个数的方法在与collect联合使用的时候特别有用。

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Long result = integerList.stream().collect(counting());
    

    findFirst

    findFirst用于查找第一个

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
    

    通过findFirst方法查找到第一个大于三的元素并打印

    findAny

    findAny用于找到任意一个

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();
    

    通过findAny方法查找到其中一个大于三的元素并打印,因为内部进行优化的原因,当找到第一个满足大于三的元素时就结束,该方法结果和findFirst方法结果一样。提供findAny方法是为了更好的利用并行流。

    reduce

    reduce用于将流中的元素组合起来。

    下面我们看一个Demo:

    int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
    int sum = integerList.stream().reduce(0, Integer::sum);
    

    reduce接受两个参数,一个初始值这里是0,一个BinaryOperator<T> accumulator来将两个元素结合起来产生一个新值,另外reduce方法还有一个没有初始化值的重载方法。

    joining

    通过joining拼接流中的元素

    String result = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));
    

    默认如果不通过map方法进行映射处理拼接的toString方法返回的字符串,joining的方法参数为元素的分界符,如果不指定生成的字符串将是一串的,可读性不强。

    获取流中最小最大值

    通过min/max获取最小最大值

    Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);
    Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);
    

    toMap

    将结果转化为map。为了介绍用方法,下面我们看一个例子:
    UserBo.java

    class UserBo{
        private int UserId;
        private String UserName;
        public UserBo(int userId, String userName) {
            super();
            UserId = userId;
            UserName = userName;
        public int getUserId() {
            return UserId;
        public void setUserId(int userId) {
            UserId = userId;
        public String getUserName() {
            return UserName;
        public void setUserName(String userName) {
            UserName = userName;
        @Override
        public String toString() {
            return "UserBo [UserId=" + UserId + ", UserName=" + UserName + "]";
    
    public class ToMapTest {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            List<UserBo> list = new ArrayList<>();
            list.add(new UserBo(100, "Mohan"));
            list.add(new UserBo(100, "Sohan"));
            list.add(new UserBo(300, "Mahesh"));
            Map<Integer, Object> map = list.stream().collect(Collectors.toMap(UserBo::getUserId, v -> v, (k, v) -> k));
            map.forEach((k, v) -> System.out.println("Key: " + k + ", value: " + v));
    ·```java
    Key: 100, value: UserBo(UserId=100, UserName=Mohan)
    Key: 300, value: UserBo(UserId=300, UserName=Mahesh)

    修改代码:
    ```java
    public class ToMapTest {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            List<UserBo> list = new ArrayList<>();
            list.add(new UserBo(100, "Mohan"));
            list.add(new UserBo(100, "Sohan"));
            list.add(new UserBo(300, "Mahesh"));
            Map<Integer, Object> map = list.stream().collect(Collectors.toMap(UserBo::getUserId, v -> v, (k, v) -> v));
            map.forEach((k, v) -> System.out.println("Key: " + k + ", value: " + v));
    

    输出结果:

    Key: 100, value: UserBo(UserId=100, UserName=Sohan)
    Key: 300, value: UserBo(UserId=300, UserName=Mahesh)
    

    我们看到toMap接受三个参数,第一个参数是转换后的map的key,第二个参数是转换后的map的value,第三个参数是当key发生冲突的时候是选择留下前面的元素还是候选的元素:其中(k, v) -> k表示选择留下前面的元素,(k, v) -> v表示选择留下后面的元素。

    最后,我们还是使用一张图,来演示下,前文的例子中,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会,在分别使用不同的最终操作可以得到怎样的结果。

    下图,展示了文中介绍的所有操作的位置、输入、输出以及使用一个案例展示了其结果。

    下图是Stream类的类结构图,里面包含了大部分的中间和终止操作。

  • 中间操作主要有以下方法(此类型方法返回的都是Stream):map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
  • 终止操作主要有以下方法:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

    本文介绍了本人在学习Java 8 Stream中的一些笔记。

    学习使用Stream要分别了解Stream创建、中间操作和最终操作。

    Stream的创建有两种方式,分别是通过集合类的stream方法、通过Stream的of方法。

    Stream的中间操作可以用来处理Stream,中间操作的输入和输出都是Stream,中间操作可以是过滤、转换、排序等。

    Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流装换成集合、以及元素的遍历等。

    最后,在Java开发中,如果使用了Java 8,那么强烈建议使用Stream。因为Stream的每个操作都可以依赖Lambda表达式,它是一种声明式的数据处理方式,并且Stream提高了数据处理效率和开发效率。

  • Java8 Stream的总结
  • java8的Stream对集合操作飞起来
  • Java 8 Stream