JDK8之Stream API
java.util.stream.*
1. 示例
我们先声明一个类
class Apple {
private int id;
private double weight;
private boolean maturity;
public Apple(int id, double weight, boolean maturity) {
this.id = id;
this.weight = weight;
this.maturity = maturity;
}
public int getId() {
return id;
}
public Apple setId(int id) {
this.id = id;
return this;
}
public double getWeight() {
return weight;
}
public Apple setWeight(double weight) {
this.weight = weight;
return this;
}
public boolean isMaturity() {
return maturity;
}
public Apple setMaturity(boolean maturity) {
this.maturity = maturity;
return this;
}
}假设我们有这个这么一个列表
List<Apple> appleList = Arrays.asList(
new Apple(1, 90, false),
new Apple(2, 95, false),
new Apple(3, 100, false),
new Apple(4, 105, false),
new Apple(5, 110, true),
new Apple(6, 115, true),
new Apple(7, 120, true),
new Apple(8, 125, true));然后我们要取重量超过100的苹果 通常应该怎么写呢?
List<Apple> appleList2 = new ArrayList<>();
for (int i = 0; i < appleList.size(); i++) {
Apple apple = appleList.get(i);
if (apple.getWeight() > 100) {
appleList2.add(apple);
}
}从语法层面上来看,这段代码确实实现了功能。但是,我们继续看,这个循环引入了一个新的变量用作索引,然后用了get方法获得该次循环的苹果,然后再判断并重量并加入新的列表。代码非常很符合java的啰里啰嗦的特点呢...而且如果某一天我的循环条件变了呢? 如果我要大于重量120,并且成熟度为true,甚至变态点我要获取某一个id的苹果...于是乎我们只能够在循环段里面不断的重写代码。
我们再来看用Stream API代替的栗子
List<Apple> appleList2 = appleList
.stream()
.filter(apple -> apple.getWeight() > 100)
.collect(Collectors.toList());这段代码比上一段代码更符合自然语言的习惯,就算我们没有接触过stream API 也可以一眼看懂代码到底做了什么。
除了语法层面上的优化,Stream API最厉害的是多核优化,几乎可以让你免费并且省力的写出多线程优化的代码。比如parallelStream()方法,顾名思义即为并行流,也就是内部迭代的代码不再是串行运行而是并行运行,提高了代码运行效率。当然,既然使用了并行运行,那么写这段迭代代码的时候还是有不少限制的。
那么下面是关于Stream API的介绍
2. Stream API
查了一下IBM上有比较专业的解释
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)
Stream并不是一个集合类,它不保存任何数据,它更像一个高级版本的Iterator(我觉得比javascript高级...)。用户不需要关注如何写出循环,只需要关注聚合条件、迭代条件...等 Stream就会自动进行内部迭代对数据做处理。
再打一个比喻,就如同流水,stream把从头遍历到位做完处理之后就不复存在了(流中源数据),就跟流水一样水流过便不会复返,可以看示例中的stream api的例子先体会体会...
1. 创建一个Stream
- Collection
Collection接口提供了默认方法 从该collection中直接获得stream:
collection.stream()
collection.parallelStream()
Array
由数组创建流。有IntStream, LongStream, DoubleStream等流。通过Array.stream(T [])获得
- 直接通过值获得
Stream.of(T... value) 三点运算符是java1.5 之后的内容,为可变参数
函数创建流
- 迭代:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) - 生成:
public static<T> Stream<T> generate(Supplier<T> s)
- 迭代:
//迭代函数
Stream.iterate(0, (x) -> x + 2).limit(3) // 0, 2, 4
//生成函数
Stream.generate(Math::random).limit(5) // 5个随机数- 还可以从其他地方生成stream比如BufferedReader.lines()
2. Stream Intermediate 中间操作
筛选/切片:
Stream<Apple> appleStream = appleList.stream();filter(Predicate p):接受一个lambda表达式,从流中过滤元素
appleStream.filter(apple -> apple.getWeight() > 100);distinct():去重,通过hashCode() 和 equals()。与set类似
appleStream.distinct();limit(long num):截取元素,使其不超过指定数量
appleStream.limit(5);skip(long num):跳过指定数量元素
appleStream.skip(5);排序:
sorted():自然排序sorted(Comparator cmp):比较器排序
映射:
map(Function fn):接受一个函数,作用到被遍历的元素上并映射成新元素(可以理解为新stream)
appleStream.map(apple -> apple.getId()).collect(Collectors.toList());flatMap(Function fn):接受一个函数,将流中的每个值都换成另一个流,然后把所有流连接成一个流(扁平化操作)
Stream
.of("hello", "world")
.flatMap((str) -> Arrays.stream(str.split("")))
.distinct()
.forEach(System.out::println);
// h, e, l, o, w, r, d- ...还有其余map请看Stream API 文档和源码
3. Stream Terminal 终止操作
forEach(Consumer c):内部迭代
appleStream.forEach(System.out::println); //apple.toString()allMatch(Predicate p):检查是否匹配所有元素
appleStream.allMatch(apple -> apple.getWeight() == 100) //falsecount():总数max(Comparator c):最大值min(Comparator c):最小值findFirst():第一个...
reduce(T iden, BinaryOperator b):将流中元素反复结合起来,得到一个值。返回 Treduce(BinaryOperator b):将流中元素反复结合起来,得到一个值。返回Optional<T>
Double sum = appleStream
.map(Apple::getWeight)
.reduce(0.0, (a, b) -> a + b); //统计苹果总重collect(Collector c):将流转换成其他形式,主要用于数据汇集
List<Double> weightList = appleStream
.map(Apple::getWeight)
.collect(Collectors.toList());
//Collectors 类提供了很多常用的静态方法供使用者使用,免去自己实现Collector 接口3. 总结
Stream 的特性可以归纳为:
不是数据结构
它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
所有 Stream 的操作必须以 lambda 表达式为参数
不支持索引访问
你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。
很容易生成数组或者 List
惰性化
很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
Intermediate 操作永远是惰性化的。
并行能力
当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
可以是无限的
集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。
参考:
