你还在为大批量数据操作而发愁吗? 你还在因看到层层for循环而感到膈应吗? 你还在为写并行代码频频出bug而懊恼吗? 快去使用Java8的Stream吧.
为啥需要用Stream
- 方便, 高效的处理集合对象
- 处理过程更加清晰, 提高可读性.
- 配合Lambda表达式, 少写代码, 提高编程效率.
- 方便编写高性能的并发程序.
应用场景
Java7写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| List<UserInfo> userInfos = new ArrayList<>(); userInfos.add(new UserInfo(1, "1", true, 1d)); userInfos.add(new UserInfo(2, "2", false, 2d)); userInfos.add(new UserInfo(3, "3", true, 3d)); userInfos.add(new UserInfo(4, "4", false, 4d));
Iterator<UserInfo> iterator = userInfos.iterator(); while (iterator.hasNext()) { UserInfo userinfo = iterator.next(); if (userinfo.isSex()) { iterator.remove(); } }
userInfos.sort(new Comparator<UserInfo>() { @Override public int compare(UserInfo o1, UserInfo o2) { if (o1.getScore() > o2.getScore()) { return 1; } else if (o1.getScore() < o2.getScore()) { return -1; } else { return 0; } } });
List<Integer> ids = new ArrayList<>(userInfos.size()); for (UserInfo userInfo : userInfos) { ids.add(userInfo.getId()); }
|
Java8写法
1 2 3 4 5 6 7 8 9 10 11 12
| List<UserInfo> userInfos = new ArrayList<>(); userInfos.add(new UserInfo(1, "1", true, 1d)); userInfos.add(new UserInfo(2, "2", false, 2d)); userInfos.add(new UserInfo(3, "3", true, 3d)); userInfos.add(new UserInfo(4, "4", false, 4d));
List<Integer> ids = userInfos.stream() .filter(s -> !s.isSex()) .sorted(Comparator.comparingDouble(UserInfo::getScore)) .map(UserInfo::getId) .collect(Collectors.toList());
|
简单的对比可以看出两者的不同, 当然是stream更胜一筹!!!
Stream详细介绍
Stream有三部分组成, 分别是数据源, 中间操作, 结束操作.
数据源生成
数据源生成常用的有如下方式:
- 从 Collection 数组
- Collection.stream()
- Collection.parallelStream()
- Arrays.stream(T array) 或者 Stream.of()
- 从 BufferdReader
- java.io.BufferedReader.lines()
- 静态工厂
- java.util.stream.IntStream.range()
- java.nio.file.Files.walk()
- 其他
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
- 等等
中间操作
Stream的中间操作只是一个标记, 一种声明, 只有在结束操作的时候才会进行真正的计算, 中间操作有两种分类:
- 无状态:元素的处理不受前面元素的影响
- unordered()
- filter()
- map()
- mapToInt()
- mapToLong()
- mapToDouble()
- flatMap()
- flatMapToInt()
- flatMapToLong()
- flatMapToDouble()
- peek()
- 有状态:必须等到所有元素处理后才会知道最后结果
- disinct()
- sorted()
- limit()
- skip()
结束操作
结束操作也有两种分类
- 非短路操作:需要处理完全部元素才能知晓结果
- forEach()
- forEachOrdered()
- toArray()
- reduce()
- collect()
- max()
- min()
- count()
- 短路操作:不用处理全部元素就可能会返回结果
- anyMatch()
- allMatch()
- noneMatch()
- findFirst()
- findAny()
常用的api
map/flatMap
把输入流的元素, 映射成另外一种元素.
对象转字符串
1 2 3 4 5
| List<String> collect = userInfos.stream().map(userInfo -> { int i = userInfo.getId(); String name = userInfo.getName(); return name + "-" + i; }).collect(Collectors.toList());
|
平方数
1 2 3 4
| List<Integer> nums = Arrays.asList(1, 2, 3, 4); List<Integer> squareNums = nums.stream(). map(n -> n * n). collect(Collectors.toList());
|
flatMap用于把有层级的结构扁平化处理.
多List扁平化输出
1 2 3
| List<Integer> collect1 = Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9)) .flatMap(Collection::stream) .collect(Collectors.toList());
|
filter
对元素进行过滤, 通过的元素会留下来生成一个新的Stream.
过滤奇数
1 2 3 4
| Integer[] sixNums = {1, 2, 3, 4, 5, 6}; List<Integer> collect2 = Stream.of(sixNums) .filter(n -> n % 2 == 1) .collect(Collectors.toList());
|
forEach/peek
遍历每个元素
遍历打印
1 2 3
| Integer[] sixNums = {1, 2, 3, 4, 5, 6}; Stream.of(sixNums) .forEach(System.out::println);
|
forEach是结束操作, 无法重复执行, 而peek可以做到重复执行.
1 2 3 4 5 6 7 8 9 10 11
| Integer[] sixNums = {1, 2, 3, 4, 5, 6}; Stream.of(sixNums) .peek(s -> { System.out.println("1-peek" + s); }) .filter(n -> n % 2 == 1) .peek(s -> { System.out.println("2-peek" + s); }).forEach(s -> { System.out.println("forEach" + s); });
|
返回内容为:
1 2 3 4 5 6 7 8 9 10 11 12
| 1-peek1 2-peek1 forEach1 1-peek2 1-peek3 2-peek3 forEach3 1-peek4 1-peek5 2-peek5 forEach5 1-peek6
|
findFirst
short-circuiting 操作, 总是返回第一个元素, 或者为空
1 2
| Optional<Integer> first = Stream.of(sixNums).findFirst(); Integer integer = first.get();
|
它返回一个Optional对象, 使用 Optional 代码的可读性更好, 而且它提供的是编译时检查, 能极大的降低 NPE 这种 Runtime Exception 对程序的影响, 或者迫使程序员更早的在编码阶段处理空值问题, 而不是留到运行时再发现和调试.
reduce
主要是用来吧元素组合起来. 需要一个起始位置和一个组合规则, sum/min/max/average都是一种reduce.
求最大
1 2 3 4 5 6 7 8
| Integer[] sixNums = {1, 7, 3, 4, 5, 6}; Integer reduce = Stream.of(sixNums).reduce(0, (a, b) -> { if (a > b) { return a; } else { return b; } });
|
limit/skip
limit 返回前n个元素; skip 扔掉前n个元素
1 2
| List<Integer> collect = Stream.of(sixNums).limit(1).collect(Collectors.toList()); List<Integer> collect1 = Stream.of(sixNums).skip(1).collect(Collectors.toList());
|
注意: 放到sorted后面会让short-circuiting失效, stream会遍历完所有的元素.
sorted
对元素排序, 使用要注意顺序, 可以先进行limit/skip, 然后对较短的集合进行排序
1
| Stream.of(sixNums).limit(3).sorted(Integer::compareTo).forEach(System.out::println);
|
min/max/distinct
找最小/最大/过滤重复
某个英文小说的词汇
1 2 3 4 5 6 7 8 9 10
| BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log")); List<String> words = br.lines(). flatMap(line -> Stream.of(line.split(" "))). filter(word -> word.length() > 0). map(String::toLowerCase). distinct(). sorted(). collect(Collectors.toList()); br.close(); System.out.println(words);
|
Match
匹配某些元素, 有三种方式:
- allMatch 全部匹配返回true, 有一个不满足就会短路, 返回false.
- anyMatch 只要有一个则返回true, 有一个满足就会短路, 返回true.
- noneMatch 没有一个则返回true, 有一个满足就会短路, 返回false.
通过名字找人
1 2 3 4 5 6 7 8 9
| List<UserInfo> userInfos = new ArrayList<>(); userInfos.add(new UserInfo(1, "1", true, 1d)); userInfos.add(new UserInfo(2, "2", false, 2d)); userInfos.add(new UserInfo(3, "3", true, 3d)); userInfos.add(new UserInfo(4, "4", false, 4d));
boolean b = userInfos.stream().anyMatch(a -> { return "1".equals(a.getName()); });
|
自己生成流
Stream.generate
随机数
1 2 3 4 5 6
| Random seed = new Random(); Supplier<Integer> random = seed::nextInt; Stream.generate(random).limit(10).forEach(System.out::println);
IntStream.generate(() -> (int) (System.nanoTime() % 100)). limit(10).forEach(System.out::println);
|
Stream.iterate
等差数列
1 2
| Stream.iterate(0, n -> n + 3).limit(10) .forEach(x -> System.out.print(x + " "));.
|
用 Collectors 来进行 reduction 操作
grooupingBy/partitioningBy/toMap
对数据分组
1 2 3 4 5 6
| Map<String, List<UserInfo>> collect = userInfos.stream().collect(Collectors.groupingBy(UserInfo::getName));
Map<Boolean, List<UserInfo>> collect1 = userInfos.stream().collect(Collectors.partitioningBy(UserInfo::isSex));
Map<Integer, UserInfo> collect2 = userInfos.stream().collect(Collectors.toMap(UserInfo::getId, userInfo -> userInfo));
|
并行处理
stream() 串行流
parallelStream() 并行流
串行流和并行流差别就是单线程和多线程的执行. 任务量小或者机器是单核的选用串行流; 任务量大且机器是多核的, 考虑使用串行流.
通过parallel()和sequential()能够实现并行流和串行流之间的切换.
并行流的使用需要符合两个规则
- 初始值必须为组合函数的恒定值
- 组合操作必须符合结合律