简介
Java 8 API添加了一个支持对元素流进行函数式操作的类 Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过 中间操作(intermediate operation)
的处理,最后由 终端操作(terminal operation)
得到前面处理的结果。
大多stream操作接受某种形式的lambda表达式作为参数,通过方法接口的形式指定操作的具体行为,这些方法接口的行为基本上都是无干扰(non-interfering)和无状态(stateless)。
- 无干扰(non-interfering):该方法不修改stream的底层数据源。
- 无状态(stateless):操作的执行是独立的,没有lambda表达式在执行中依赖可能发生变化的外部变量或状态。
stream的创建
- 从一个 Collection 的 stream() 和 parallelStream() 方法;
- 从一个数组通过 Arrays.stream(Object[]);
- 来自流类上的静态工厂方法,例如 Stream.of(Object[]), IntStream.range(int, int) 或Stream.iterate(Object, UnaryOperator);
- 文件的行可以从 BufferedReader.lines() 获得;
- 文件路径流可以从 Files 中的方法获得;
- 可以从 Random.ints() 获得随机数流;
- JDK 中的许多其他流承载方法,包括 BitSet.stream()、Pattern.splitAsStream(java.lang.CharSequence) 和 JarFile.stream()。
private static void createStream() {
List<String> list = new ArrayList<String>() {//这个大括号 就相当于我们 new 接口
{//这个大括号 就是 构造代码块 会在构造函数前 调用
this.add("hi");
this.add("sorry");
this.add("hello");
this.add("word");
}
};
// 从一个 Collection 的 stream() 和 parallelStream() 方法
list.stream().map(it -> it + " dd").forEach(System.out::println);
// 并发不保证执行顺序
list.parallelStream().map(it -> it + " dd").forEach(System.out::println);
String[] array = new String[]{
"one", "two", "three"
};
// 从一个数组通过 Arrays.stream(Object[])
Arrays.stream(array).map(it -> it + " dd").forEach(System.out::println);
// 来自流类上的静态工厂方法
IntStream.range(1, 5).forEach(System.out::println);
// 无限流
Stream.iterate(1, s -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return s + 1;
}).forEach(System.out::println);
// 文件的行可以从 BufferedReader.lines() 获得
try {
new BufferedReader(new FileReader("./test.txt")).lines().forEach(System.out::println);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 从 Random.ints() 获得随机数流
new Random().ints(3, 1, 20).forEach(System.out::println);
}
简单使用
中间操作
- 过滤:
filter(T -> boolean)
(非干扰、无状态) - 类型转换:
map(T -> R)
将流中的每一个元素 T 映射为 R (非干扰、无状态) flatMap(T -> Stream)
将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流distinct()
将流中重复的元素去除,去重 (非干扰、有状态)- 根据字段排序
sorted() / sorted((T, T) -> int)
(非干扰、有状态) peek(T -> void)
返回由该流的元素组成的流, 这个方法存在主要是为了支持调试,你想要的地方查看流经管道中某个点的元素- 返回前 n 个元素
limit(long n)
(短路、有状态) - 跳过前 n 个元素
skip(long n)
(有状态)
private static void intermediateOperation() {
String[] array = new String[]{
"one, two", "four, five", "two, three", "three, four", "one, two", "six", "ten", "five", "three"
};
Arrays.stream(array)
.filter(s -> s.startsWith("t"))
.forEach(System.out::println);
Arrays.stream(array)
.map(s -> s.split(","))
.flatMap(Arrays::stream)
.peek(s -> System.out.println("flatMap -> " + s))
.map(String::trim)
.distinct()
.sorted((String::compareTo))
.limit(3)
.skip(1)
.forEach(System.out::println);
}
终端操作
- 遍历操作
forEach(e -> { … }) / forEachOrdered(e -> { … })
toArray()
返回一个包含此流元素的数组。reduce((T, T) -> T)
、reduce(T, (T, T) -> T)
和reduce(R, (R, T) -> R, (R, R) -> R)
用于组合流中的元素,如求和,求积,求最大值等collect()
收集方法,我们很常用的是 collect(toList()),当然还有 collect(toSet()) 等,参数是一个收集器接口min()
、max()
、count()
最小值、最大值、流中元素数量anyMatch(T -> boolean)
流中是否有一个元素匹配给定的 T -> boolean 条件allMatch(T -> boolean)
流中是否所有元素都匹配给定的 T -> boolean 条件noneMatch(T -> boolean)
流中是否没有元素匹配给定的 T -> boolean 条件findAny()
:找到其中一个元素, 使用stream()
时找到的是第一个元素;parallelStream()
并行时找到的是其中一个元素;findFirst()
:找到第一个元素; 值得注意的是,这两个方法返回的是一个Optional
对象,它是一个容器类,能代表一个值存在或不存在
private static void terminalOperation() {
int[] array = new int[]{
1, 2, 3, 4, 5, 2, 1, 3, 7
};
// reduce((T, T) -> T) 不接受任何起始值,但因为没有初始值,需要考虑结果可能不存在的情况,因此返回的是 Optional 类型
OptionalInt res = Arrays.stream(array).reduce(Integer::sum);
// reduce(T, (T, T) -> T) reduce 第一个参数 0 代表起始值为 0
int res1 = Arrays.stream(array).reduce(0, Integer::sum);
List<Widget> widgetList = Widget.genRandomWidgets(10);
// reduce(R, (R, T) -> R, (R, R) -> R)
int sumOfWeights = widgetList.stream().reduce(0, (sum, b) -> sum + b.getWeight(), Integer::sum);
System.out.println("res = " + res + " res1 = " + res1 + " sumOfWeights = " + sumOfWeights);
ArrayList<String> strings = Arrays.stream(array).collect(ArrayList::new, (c, e) -> c.add(e + ""), ArrayList::addAll);
System.out.println(strings);
List<String> weights = widgetList.stream().map(t -> t.getWeight() + "").collect(Collectors.toList());
System.out.println(weights);
OptionalInt min = Arrays.stream(array).min();
OptionalInt max = Arrays.stream(array).max();
long count = Arrays.stream(array).count();
System.out.println("min = " + min + " max = " + max + " count = " + count);
boolean anyMatch = Arrays.stream(array).anyMatch(t -> t > 5);
boolean allMatch = Arrays.stream(array).allMatch(t -> t > 5);
boolean noneMatch = Arrays.stream(array).noneMatch(t -> t > 5);
System.out.println("anyMatch = " + anyMatch + " allMatch = " + allMatch + " noneMatch = " + noneMatch);
OptionalInt findAny = Arrays.stream(array).parallel().findAny();
OptionalInt findFirst = Arrays.stream(array).findFirst();
System.out.println("findAny = " + findAny + " findFirst = " + findFirst);
}
消耗品(Consumable)
流的元素在流的生命周期内仅被访问一次
。与 Iterator 一样,必须生成新的流以重新访问源的相同元素。
String[] array = new String[]{
"one, two", "four"
};
Stream<String> stream = Stream.of(array);
stream.findFirst();
stream.findAny();
在执行上述代码时, 由于第一个终端操作 findFirst
已经消耗掉 stream, 导致执行第二个终端操作 findAny
时会抛出如下异常:
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.ReferencePipeline.findAny(ReferencePipeline.java:469)
at com.lkl.study.feature.stream.StreamTest.consumableTest(StreamTest.java:125)
at com.lkl.study.feature.stream.StreamTest.main(StreamTest.java:16)
可以通过为每个最终操作(terminal operation)
创建一个新的stream流的方式来解决上面的重用问题。
String[] array = new String[]{
"one, two", "four"
};
Stream<String> stream = Stream.of(array);
stream.findFirst();
stream = Stream.of(array);
stream.findAny();
Supplier类可以在已经存在的中间操作(intermediate operations )
的stream基础上构建一个新的stream。
Supplier<Stream<String>> streamSupplier =
() -> Stream.of(array)
.filter(s -> s.startsWith("t"));
System.out.println(streamSupplier.get().anyMatch(s -> true));
System.out.println(streamSupplier.get().noneMatch(s -> true));
streamSupplier的每次get()方法会构造一个新的stream,我们可以在这个stream上执行期望的最终操作(terminal operation)
。