java8 stream 常用方法
预备知识
Stream上的所有操作分为两类:
中间操作和结束操作,中间操作只是一种标记,只有结束操作才会触发实际计算。
中间操作又可以分为无状态的和有状态的:
无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果;
结束操作又可以分为短路操作和非短路操作
短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。
操作类型 | 状态类型 | 方法 |
---|---|---|
中间操作(Intermediate operations) | 无状态(Stateless) | unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek() |
中间操作(Intermediate operations) | 有状态(Stateful) | distinct() sorted() sorted() limit() skip() |
结束操作(Terminal operations) | 非短路操作 | forEach() forEachOrdered() toArray() reduce() collect() max() min() count() |
结束操作(Terminal operations) | 短路操作(short-circuiting) | anyMatch() allMatch() noneMatch() findFirst() findAny() |
intermediate operation
map,mapToInt,...
map 对于流中的每个对象,对应输出成另一个对象的流
mapToInt 输出成IntStream
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3)
, new Cat("小花", "暹罗", 4)
);
List<String> names = cats.stream().map(Cat::getType).collect(Collectors.toList());
Integer maxAge = cats.stream().mapToInt(Cat::getAge).max().orElse(0);
// console
// [中华田园, 波斯猫, 暹罗, 暹罗]
// 10
flatMap,flatMapToInt
对于每个元素生成一个新的stream,并把它们扁平化(flat)
flatMap
List<Integer> l1 = Lists.newArrayList(3, 5, 1, 2, 6);
List<Integer> l2 = Lists.newArrayList(4, 8, 7, 9, 0);
List<List<Integer>> l = Lists.newArrayList(l1, l2);
System.out.println(l);
System.out.println(l.stream().flatMap(Collection::stream).sorted().map(n -> n * n).collect(Collectors.toList()));
// console
[[3, 5, 1, 2, 6], [4, 8, 7, 9, 0]]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
peek
和map类似,但是map的参数是个带返回值的function,peek是个不带返回值的Consumer
这里有个坑,如果只使用peek而没有后续操作的话,peek里面的方法不会被执行。可以看peek方法的apiNote
This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3)
, new Cat("小花", "暹罗", 4)
);
cats.stream().peek(System.out::println).count();
System.out.println("========================================");
cats.stream().peek(System.out::println);
System.out.println("----------------------------------------");
// console
// Cat(name=咪咪, type=中华田园, age=10)
// Cat(name=喵喵, type=波斯猫, age=5)
// Cat(name=阿花, type=暹罗, age=3)
// Cat(name=小花, type=暹罗, age=4)
// ========================================
// ----------------------------------------
filter
过滤出符合条件对象组成stream
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3)
, new Cat("小花", "暹罗", 4)
);
System.out.println(cats.stream().filter(n -> "波斯猫".equals(n.getType())).collect(Collectors.toList()));
// console
// [Cat(name=喵喵, type=波斯猫, age=5)]
distinct
去除相同的元素
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3)
, new Cat("小花", "暹罗", 4)
);
System.out.println(cats.stream().map(Cat::getType).distinct().collect(Collectors.toList()));
// console
// [中华田园, 波斯猫, 暹罗]
skip 跳过n个元素
limit 限制n个元素
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3)
, new Cat("小花", "暹罗", 4)
);
System.out.println(cats.stream().limit(3).collect(Collectors.toList()));
System.out.println(cats.stream().skip(2).collect(Collectors.toList()));
// [Cat(name=咪咪, type=中华田园, age=10), Cat(name=喵喵, type=波斯猫, age=5), Cat(name=阿花, type=暹罗, age=3)]
// [Cat(name=阿花, type=暹罗, age=3), Cat(name=小花, type=暹罗, age=4)]
sort 排序
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3)
, new Cat("小花", "暹罗", 4)
);
// System.out.println(cats.stream().sorted().collect(Collectors.toList())); // Cat 类没有实现Comparable接扣,会报错
System.out.println(cats.stream().sorted(Comparator.comparing(Cat::getAge)).collect(Collectors.toList()));
System.out.println(cats.stream().sorted(Comparator.comparing(Cat::getAge).reversed()).collect(Collectors.toList()));
// console
// [Cat(name=阿花, type=暹罗, age=3), Cat(name=小花, type=暹罗, age=4), Cat(name=喵喵, type=波斯猫, age=5), Cat(name=咪咪, type=中华田园, age=10)]
// [Cat(name=咪咪, type=中华田园, age=10), Cat(name=喵喵, type=波斯猫, age=5), Cat(name=小花, type=暹罗, age=4), Cat(name=阿花, type=暹罗, age=3)]
unordered
用来消除遇到顺序
不建议使用
If a stream is ordered, repeated execution of identical stream pipelines on an identical source will produce an identical result; if it is not ordered, repeated execution might produce different results.
遇到顺序(encounter order)
https://www.ibm.com/developerworks/cn/java/j-java-streams-3-brian-goetz/index.html
遇到顺序
另一个影响库的优化能力的微妙的考虑事项是遇到顺序。遇到顺序指的是来源分发元素的顺序是否对计算至关重要。一些来源(比如基于哈希的集合和映射)没有有意义的遇到顺序。流标志 ORDERED 描述了流是否有有意义的遇到顺序。JDK 集合的 spliterator 会根据集合的规范来设置此标志;一些中间操作可能注入 ORDERED (sorted()) 或清除它 (unordered())。
如果流没有遇到顺序,大部分流操作都必须遵守该顺序。对于顺序执行,会自动保留遇到顺序,因为元素会按遇到它们的顺序自然地处理。甚至在并行执行中,许多操作(无状态中间操作和一些终止操作(比如 reduce())),遵守遇到顺序不会产生任何实际成本。但对于其他操作(有状态中间操作,其语义与遇到顺序关联的终止操作,比如 findFirst() 或 forEachOrdered()),在并行执行中遵守遇到顺序的责任可能很重大。如果流有一个已定义的遇到顺序,但该顺序对结果没有意义,那么可以通过使用 unordered() 操作删除 ORDERED 标志,加速包含顺序敏感型操作的管道的顺序执行。
作为对遇到顺序敏感的操作的示例,可以考虑 limit(),它会在指定大小处截断一个流。在顺序执行中实现 limit() 很简单:保留一个已看到多少元素的计数器,在这之后丢弃任何元素。但是在并行执行中,实现 limit() 要复杂得多;您需要保留前 N 个元素。此要求大大限制了利用并行性的能力;如果输入划分为多个部分,您只有在某个部分之前的所有部分都已完成后,才知道该部分的结果是否将包含在最终结果中。因此,该实现一般会错误地选择不使用所有可用的核心,或者缓存整个试验性结果,直到您达到目标长度。
如果流没有遇到顺序,limit() 操作可以自由选择任何 N 个元素,这让执行效率变得高得多。知道元素后可立即将其发往下游,无需任何缓存,而且线程之间唯一需要执行的协调是发送一个信号来确保未超出目标流长度。
遇到顺序成本的另一个不太常见的示例是排序。如果遇到顺序有意义,那么 sorted() 操作会实现一种稳定 排序(相同的元素按照它们进入输入时的相同顺序出现在输出中),而对于无序的流,稳定性(具有成本)不是必需的。distinct() 具有类似的情况:如果流有一个遇到顺序,那么对于多个相同的输入元素,distinct() 必须发出其中的第一个,而对于无序的流,它可以发出任何元素 — 同样可以获得高效得多的并行实现。
在您使用 collect() 聚合时会遇到类似的情形。如果在无序流上执行 collect(groupingBy()) 操作,与任何键对应的元素都必须按它们在输入中出现的顺序提供给下游收集器。此顺序对应用程序通常没有什么意义,而且任何顺序都没有意义。在这些情况下,可能最好选择一个并发 收集器(比如 groupingByConcurrent()),它可以忽略遇到顺序,并让所有线程直接收集到一个共享的并发数据结构中(比如 ConcurrentHashMap),而不是让每个线程收集到它自己的中间映射中,然后再合并中间映射(这可能产生很高的成本)。
Set<Integer> l = new TreeSet<>();
l.add(1);
l.add(10);
l.add(3);
l.add(-3);
l.add(-4);
System.out.println("Serial Stream");
l.stream().map(s->s+" ").forEach(System.out::print);
System.out.println();
l.stream().unordered().map(s->s+" ").forEach(System.out::print);
System.out.println("\n");
System.out.println("Unordered Operations on a Parallel Stream");
l.stream().parallel().map(s->s+" ").forEach(System.out::print);
System.out.println();
l.stream().unordered().map(s->s+" ").parallel().forEach(System.out::print);
System.out.println("\n");
System.out.println("Ordered Operations on a Parallel Stream");
l.stream().parallel().skip(2).limit(2).findFirst().ifPresent(System.out::print);
System.out.println();
l.stream().unordered().parallel().skip(2).limit(2).findFirst().ifPresent(System.out::print);
System.out.println("\n");
// console
Serial Stream
-4 -3 1 3 10
-4 -3 1 3 10
Unordered Operations on a Parallel Stream
3 10 -3 1 -4
3 10 -4 -3 1
Ordered Operations on a Parallel Stream
1
-3
allMatch 任意一个符合,返回true
anyMatch 所有的都符合,返回true
noneMatch 所有的都不符合,返回true
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3)
, new Cat("小花", "暹罗", 4)
);
System.out.println(cats.stream().allMatch(n -> "波斯猫".equals(n.getType())));
System.out.println(cats.stream().anyMatch(n -> "波斯猫".equals(n.getType())));
System.out.println(cats.stream().noneMatch(n -> "波斯猫".equals(n.getType())));
System.out.println(cats.stream().allMatch(n -> n.getName().length() == 2));
System.out.println(cats.stream().anyMatch(n -> n.getName().length() == 2));
System.out.println(cats.stream().noneMatch(n -> n.getName().length() == 2));
System.out.println(cats.stream().allMatch(n -> n.getAge() > 12));
System.out.println(cats.stream().anyMatch(n -> n.getAge() > 12));
System.out.println(cats.stream().noneMatch(n -> n.getAge() > 12));
// console
// false
// true
// false
// true
// true
// false
// false
// false
// true
onClose
流关闭的时候可以执行一个Runnable
Stream<Integer> stream = Lists.newArrayList(1, 2, 3, 4, 5).stream();
stream.onClose(() -> {
System.out.println("stream is closed");
});
List<Integer> collect = stream.map(n -> n * n).collect(Collectors.toList());
System.out.println(collect);
stream.close();
// concole
[1, 4, 9, 16, 25]
stream is closed
parallel 并行流
sequential 串行流
terminal operation
findFirst,findAny
findFirst返回一个第一个对象的Optional对象
findAny返回任意一个对象的Optional对象
在stream里两个方法里面没有区别,都是返回第一个对象
在parallelStream里面any是真的any
@Data
@AllArgsConstructor
public class Cat {
private String name;
private String type;
private int age;
}
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗猫", 3));
System.out.println(cats.stream().findFirst());
System.out.println(cats.parallelStream().findFirst());
System.out.println(cats.stream().findAny());
System.out.println(cats.parallelStream().findAny()); // 每次执行结果不一定
// console
// Optional[Cat(name=咪咪, type=中华田园, age=10)]
// Optional[Cat(name=咪咪, type=中华田园, age=10)]
// Optional[Cat(name=咪咪, type=中华田园, age=10)]
// Optional[Cat(name=喵喵, type=波斯猫, age=5)]
min,max,count
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中华田园", 10)
, new Cat("喵喵", "波斯猫", 5)
, new Cat("阿花", "暹罗", 3));
System.out.println(cats.stream().min(Comparator.comparingInt(Cat::getAge)));
System.out.println(cats.stream().max(Comparator.comparingInt(c -> c.getType().length())));
System.out.println(cats.stream().filter(n -> n.getAge() < 5).count());
// console
// Optional[Cat(name=阿花, type=暹罗, age=3)]
// Optional[Cat(name=咪咪, type=中华田园, age=10)]
// 1
forEach,forEachOrdered
遍历元素,在串行遍历的时候,没有区别,在并行遍历的时候,forEach不能保证顺序
cList<Integer> l = Lists.newArrayList(3, 5, 2, 4, 1);
l.stream().forEach(System.out::print);
System.out.println();
l.stream().forEachOrdered(System.out::print);
System.out.println();
l.stream().parallel().forEach(System.out::print);
System.out.println();
l.stream().parallel().forEachOrdered(System.out::print);
// console
35241
35241
25341 // 每次执行结果有可能不一样
35241
toArray
把stream转换成一个数组
Cat c1 = new Cat("咪咪", "中华田园", 10);
Cat c2 = new Cat("喵喵", "波斯猫", 5);
Cat c3 = new Cat("阿花", "暹罗", 3);
Cat c4 = new Cat("小花", "暹罗", 4);
Cat c5 = new Cat("小白", "蓝猫", 6);
Cat c6 = new Cat("小黑", "美短", 15);
Cat c7 = new Cat("小黄", "英短", 1);
Cat c8 = new Cat("小粉", "暹罗", 1);
List<Cat> cats = Lists.newArrayList(
c1, c2, c3, c4, c5, c6, c7, c8
);
Object[] objects = cats.stream().map(Cat::getAge).toArray();
for (Object object : objects) {
System.out.print(object + " ");
}
System.out.println();
Integer[] ages = cats.stream().map(Cat::getAge).toArray(Integer[]::new);
for (Integer age : ages) {
System.out.print(age + " ");
}
// console
10 5 3 4 6 15 1 1
10 5 3 4 6 15 1 1
reduce 累加操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<String> strs = Lists.newArrayList("a", "b", "c", "d", "e");
int result1 = numbers.stream().reduce(100, (subtotal, element) -> subtotal + element);
String result2 = strs.stream().reduce((result, word) -> result + "_" + word).orElse("");
System.out.println(result1);
System.out.println(result2);
int result3 = numbers.stream().reduce((subtotal, element) -> subtotal + element).orElse(0);
String result4 = strs.stream().reduce("start", (result, word) -> result + "_" + word);
System.out.println(result3);
System.out.println(result4);
// console
121
a_b_c_d_e
21
start_a_b_c_d_e
collect 结果收集
Cat c1 = new Cat("咪咪", "中华田园", 10);
Cat c2 = new Cat("喵喵", "波斯猫", 5);
Cat c3 = new Cat("阿花", "暹罗", 3);
Cat c4 = new Cat("小花", "暹罗", 4);
List<Cat> cats = Lists.newArrayList(
c1, c2, c3, c4
);
// toList
List<String> collect = cats.stream().map(Cat::getName).collect(Collectors.toList());
System.out.println("toList:" + collect);
// toSet
Set<String> set = cats.stream().map(Cat::getType).collect(Collectors.toSet());
System.out.println("toSet:" + set);
// toMap
Map<String, Cat> map = cats.stream().collect(Collectors.toMap(Cat::getName, n -> n));
System.out.println("toMap:" + map);
// 会报错提示 Duplicate key,key不能重复
// Map<String, Cat> map2 = cats.stream().collect(Collectors.toMap(Cat::getType, n -> n));
// System.out.println(map2);
// groupBy
Map<String, List<Cat>> groupBy = cats.stream().collect(Collectors.groupingBy(Cat::getType));
System.out.println("groupBy:" + groupBy);
// partitioningBy
Map<Boolean, List<Cat>> partitioningBy = cats.stream().collect(Collectors.partitioningBy(n -> n.getAge() > 5));
System.out.println("partitioningBy:" + partitioningBy);
// console
toList:[咪咪, 喵喵, 阿花, 小花]
toSet:[暹罗, 波斯猫, 中华田园]
toMap:{阿花=Cat(name=阿花, type=暹罗, age=3), 小花=Cat(name=小花, type=暹罗, age=4), 喵喵=Cat(name=喵喵, type=波斯猫, age=5), 咪咪=Cat(name=咪咪, type=中华田园, age=10)}
groupBy:{暹罗=[Cat(name=阿花, type=暹罗, age=3), Cat(name=小花, type=暹罗, age=4)], 波斯猫=[Cat(name=喵喵, type=波斯猫, age=5)], 中华田园=[Cat(name=咪咪, type=中华田园, age=10)]}
partitioningBy:{false=[Cat(name=喵喵, type=波斯猫, age=5), Cat(name=阿花, type=暹罗, age=3), Cat(name=小花, type=暹罗, age=4)], true=[Cat(name=咪咪, type=中华田园, age=10)]}