乐趣区

玩转Java8中的-Stream-之从零认识-Stream

作者:litesky

来源:http://www.jianshu.com/p/11c9…

相信 Java8 的 Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,文章将带大家从零开始使用,循序渐进,带你走向 Stream 的巅峰。

操作符

什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。

Stream 的操作符大体上分为两种:中间操作符和终止操作符

中间操作符

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

中间操作符包含 8 种(排除了 parallel,sequential, 这两个操作并不涉及到对数据流的加工操作):

map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如 A ->B,这里默认提供了转 int,long,double 的操作符。
flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了 3 个数据,这里默认提供了拍平成 int,long,double 的操作符。
limit 限流操作,比如数据流中有 10 个 我只要出前 3 个就可以使用。
distint 去重操作,对重复元素去重,底层使用了 equals 方法。
filter 过滤操作,把不想要的数据过滤。
peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。
skip 跳过操作,跳过某些元素。
sorted(unordered) 排序操作,对元素排序,前提是实现 Comparable 接口,当然也可以自定义比较器。

终止操作符

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的 Collectors 提供了非常多收集器,可以说 Stream 的核心在于 Collectors。
count 统计操作,统计最终的数据个数。
findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为 Optional。
noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为 bool 值。
min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。
reduce 规约操作,将整个数据流的值规约为一个值,count、min、max 底层就是使用 reduce。
forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。
toArray 数组操作,将数据流的元素转换成数组。
这里只介绍了 Stream,并没有涉及到 IntStream、LongStream、DoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。Java 知音公众号内回复“面试题聚合”,送你一份各大公司面试汇总宝典。

说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。

代码演练

Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。

map,可以看到 map 操作符要求输入一个 Function 的函数是接口实例,功能是将 T 类型转换成 R 类型的。

map 操作将原来的单词 转换成了每个单的长度,利用了 String 自身的 length()方法,该方法返回类型为 int。这里我直接使用了 lambda 表达式,关于 lambda 表达式 还请读者们自行了解吧。

public class Main {public static void main(String[] args) {Stream.of("apple","banana","orange","waltermaleon","grape")
                .map(e->e.length()) // 转成单词的长度 int
                .forEach(e->System.out.println(e)); // 输出
    }
}

当然也可以这样,这里使用了成员函数引用,为了便于读者们理解,后续的例子中将使用 lambda 表达式而非函数引用。

public class Main {public static void main(String[] args) {Stream.of("apple","banana","orange","waltermaleon","grape")
                .map(String::length) // 转成单词的长度 int
                .forEach(System.out::println);
    }
}

结果如图:

mapToInt 将数据流中得元素转成 Int,这限定了转换的类型 Int,最终产生的流为 IntStream,及结果只能转化成 int。

public class Main {public static void main(String[] args) {Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToInt(e -> e.length()) // 转成 int
                .forEach(e -> System.out.println(e));
    }
}

mapToInt 如图:

mapToLong、mapToDouble 与 mapToInt 类似

public class Main {public static void main(String[] args) {Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToLong(e -> e.length()) // 转成 long , 本质上是 int 但是存在类型自动转换
                .forEach(e -> System.out.println(e));
    }
}

mapToLong 如图:

public class Main {public static void main(String[] args) {Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToDouble(e -> e.length()) // 转成 Double,自动类型转换成 Double
                .forEach(e -> System.out.println(e));
    }
}

mapToDouble 如图:

flatmap 作用就是将元素拍平拍扁,将拍扁的元素重新组成 Stream,并将这些 Stream 串行合并成一条 Stream

public class Main {public static void main(String[] args) {Stream.of("a-b-c-d","e-f-i-g-h")
                .flatMap(e->Stream.of(e.split("-")))
                .forEach(e->System.out.println(e));

    }
}

flatmap 如图:

flatmapToInt、flatmapToLong、flatmapToDouble 跟 flatMap 都类似的,只是类型被限定了,这里就不在举例子了。

limit 限制元素的个数,只需传入 long 类型 表示限制的最大数

public class Main {public static void main(String[] args) {Stream.of(1,2,3,4,5,6)
                .limit(3) // 限制三个
                .forEach(e->System.out.println(e)); // 将输出 前三个 1,2,3
    }
}

limit 如图:

public class Main {public static void main(String[] args) {Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
                .distinct() // 去重
                .forEach(e->System.out.println(e));

    }
}

distinct 如图:

filter 对某些元素进行过滤,不符合筛选条件的将无法进入流的下游

public class Main {public static void main(String[] args) {Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
                .filter(e->e>=5) // 过滤小于 5 的
                .forEach(e->System.out.println(e));
    }
}

filter 如图:

peek 挑选,将元素挑选出来,可以理解为提前消费

public class Main {public static void main(String[] args) {User w = new User("w",10);
        User x = new User("x",11);
        User y = new User("y",12);

        Stream.of(w,x,y)
                .peek(e->{e.setName(e.getAge()+e.getName());}) // 重新设置名字 变成 年龄 + 名字
                .forEach(e->System.out.println(e.toString()));

    }

    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {return name;}

        public void setName(String name) {this.name = name;}

        public int getAge() {return age;}

        public void setAge(int age) {this.age = age;}

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

peek 如图:

skip 跳过 元素

public class Main {public static void main(String[] args) {Stream.of(1,2,3,4,5,6,7,8,9)
                .skip(4) // 跳过前四个
                .forEach(e->System.out.println(e)); // 输出的结果应该只有 5,6,7,8,9
    }
}

skip 如图:

sorted 排序 底层依赖 Comparable 实现,也可以提供自定义比较器
这里 Integer 实现了比较器

public class Main {public static void main(String[] args) {Stream.of(2,1,3,6,4,9,6,8,0)
                .sorted()
                .forEach(e->System.out.println(e));
    }
}

sorted 默认比较器如图:

这里使用自定义比较,当然 User 可以实现 Comparable 接口

public class Main {public static void main(String[] args) {User x = new User("x",11);
        User y = new User("y",12);
        User w = new User("w",10);

        Stream.of(w,x,y)
                .sorted((e1,e2)->e1.age>e2.age?1:e1.age==e2.age?0:-1)
                .forEach(e->System.out.println(e.toString()));

    }

    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {return name;}

        public void setName(String name) {this.name = name;}

        public int getAge() {return age;}

        public void setAge(int age) {this.age = age;}

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

如图:

collect 收集,使用系统提供的收集器可以将最终的数据流收集到 List,Set,Map 等容器中。
这里我使用 collect 将元素收集到一个 set 中

public class Main {public static void main(String[] args) {Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .collect(Collectors.toSet()) //set 容器
                .forEach(e -> System.out.println(e));
    }
}

咦?,不是说终止操作符只能使用一次吗,为什么这里调用了 forEach 呢?forEach 不仅仅是是 Stream 中得操作符还是各种集合中得一个语法糖,不信咋们试试。Java 知音公众号内回复“面试题聚合”,送你一份各大公司面试汇总宝典。

public class Main {public static void main(String[] args) {Set<String> stringSet = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .collect(Collectors.toSet()); // 收集的结果就是 set
        stringSet.forEach(e->System.out.println(e)); set 的语法糖 forEach
}

结果如图:

count 统计数据流中的元素个数,返回的是 long 类型

public class Main {public static void main(String[] args) {long count = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .count();

        System.out.println(count);
    }
}

count 如图:

findFirst 获取流中的第一个元素
这里找到第一个元素 apple

public class FindFirst {public static void main(String[] args) {Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .findFirst();
        stringOptional.ifPresent(e->System.out.println(e));
    }
}

findFirst 结果如图:

findAny 获取流中任意一个元素

public class FindAny {public static void main(String[] args) {Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .parallel()
                .findAny(); // 在并行流下每次返回的结果可能一样也可能不一样
        stringOptional.ifPresent(e->System.out.println(e));
    }
}

findAny 在并行流下 使用结果:

输出了 orange

输出了 banana

noneMatch 数据流中得没有一个元素与条件匹配的
这里 的作用是是判断数据流中 一个都没有与 aa 相等元素,但是流中存在 aa,所以最终结果应该是 false

public class NoneMatch {public static void main(String[] args) {boolean result = Stream.of("aa","bb","cc","aa")
                .noneMatch(e->e.equals("aa"));
        System.out.println(result);
    }
}

noneMatch 如图:

allMatch 和 anyMatch 一个是全匹配,一个是任意匹配 和 noneMatch 类似,这里就不在举例了。

min 最小的一个,传入比较器,也可能没有(如果数据流为空)

public class Main {public static void main(String[] args) {Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
                .min((e1,e2)->e1.compareTo(e2));

        integerOptional.ifPresent(e->System.out.println(e));

    }

min 如图:

max 元素中最大的,需要传入比较器,也可能没有(流为 Empty 时)

public class Main {public static void main(String[] args) {Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
                .max((e1,e2)->e1.compareTo(e2));

        integerOptional.ifPresent(e->System.out.println(e));

    }
}

max 如图:

reduce 是一个规约操作,所有的元素归约成一个,比如对所有元素求和,乘啊等。
这里实现了一个加法,指定了初始化的值

public class Main {public static void main(String[] args) {int sum = Stream.of(0,9,8,4,5,6,-1)
              .reduce(0,(e1,e2)->e1+e2);
        System.out.println(sum);
    }
}

reduce 如图:

forEach
forEach 其实前就已经见过了,对每个数据遍历迭代

forEachOrdered 适用用于并行流的情况下进行迭代,能保证迭代的有序性
这里通过并行的方式输出数字

public class ForEachOrdered {public static void main(String[] args) {Stream.of(0,2,6,5,4,9,8,-1)
                .parallel()
                .forEachOrdered(e->{System.out.println(Thread.currentThread().getName()+":"+e);});
    }
}

forEachOrdered 如图:

toArray 转成数组,可以提供自定义数组生成器

public class ToArray {public static void main(String[] args) {Object[] objects=Stream.of(0,2,6,5,4,9,8,-1)
                .toArray();

        for (int i = 0; i < objects.length; i++) {System.out.println(objects[i]);
        }
    }
}

toArray 如图:

总结

Java8 Stream 就带大家认识到这里,如果你能跟着我的文章把每一个例子都敲一遍,相信都能掌握这些操作符的初步用法。

微信搜索:Java 小咖秀回复“手册”,获取 Java 核心面试手册 +linux 实用命令手册一份。

退出移动版