2.1 从迭代器到Stream操作
List<String> words = ...;
for(String w: words){
if(w.length()>12) count++;
}
普通的迭代器,很难被并行计算。这也是java8引入大量操作符的原因。
List<String> s = new ArrayList<>();
long count = s.stream().filter(w -> w.length()>12).count();
注意,只有List< T >中的泛型确定,w里方法才不会报错。
stream方法会为单词列表生成一个Stream。filter方法会返回另一个只包含单词长度大于12的Stream。count方法会将Stream化简成一个结果。
1.Stream自己不会存储元素。元素可能被存储在底层的集合中。
2.Stream操作符不会改变原对象,相反,它们会返回一个持有结果的新的Stream。
3.Stream可能是延迟执行的。
以下是并行统计的写法:
long count = s.parallelStream().filter(w -> w.length()>12).count();
使用流水线:
- 创建Stream(流) (2.2)
- 在一个或多个步骤中,将初始Stream转化为另一个Stream的中间操作,如filter (2.3-2.5)
- 终止操作,会强制它之前的延迟操作立即执行。如count(2.6)
数据流操作要么是衔接操作,要么是终止操作。衔接操作返回数据流,所以我们可以把多个衔接操作不使用分号来链接到一起。终止操作无返回值,或者返回一个不是流的结果。在上面的例子中,filter、map和sorted都是衔接操作,而forEach是终止操作。列你在上面例子中看到的这种数据流的链式操作也叫作操作流水线。
多数数据流操作都接受一些lambda表达式参数,函数式接口用来指定操作的具体行为。这些操作的大多数必须是无干扰而且是无状态的。它们是什么意思呢?
当一个函数不修改数据流的底层数据源,它就是无干扰的。例如,在上面的例子中,没有任何lambda表达式通过添加或删除集合元素修改myList。
当一个函数的操作的执行是确定性的,它就是无状态的。例如,在上面的例子中,没有任何lambda表达式依赖于外部作用域中任何在操作过程中可变的变量或状态。
2.2 创建Stream
java8在Collection接口中添加了stream方法,可以将任何一个集合转化为Stream。
如果是数组,也可以用静态的Stream.of方法得到一个Stream。
Stream<String> song = Stream.of("a","b","c");
empty(),创建一个不含任何元素的Stream
public static<T> Stream<T> empty() {
return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
}
2.3 filter map flatMap方法
流转换是指从一个流中读取数据,并将转换后的数据写入到另一个流中。
filter方法:顾名思义,过滤,并得到一个满足要求的新Stream
Stream<T> filter(Predicate<? super T> predicate);//所以这里可以用lambda表达式
@FunctionalInterface
public interface Predicate<T> {
/** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */
boolean test(T t);
map方法:对流中的元素进行某种形式的转换,但是输出是以流对象为基本单位(?)
Stream<String> lowercaseWords = s.stream().map(String::toLowerCase);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
public interface Function<T, R> {
/** * Applies this function to the given argument. * * @param t the function argument * @return the function result */
R apply(T t);
flatMap:以元素为基本单位,合并成一个流再返回
public static Stream<Character> characterStream(String
s){
...
}
Stream<Stream<Character>> temp1 = s.stream().map(w-> characterStream(w)); //只能输出一个包含多个流的流
Stream<Character> letters = s.stream().flatMap(w-> characterStream(w)); //可以输出一个只包含字符串的流
2.4 提取子流和组合流
limit(long maxSize) 裁剪出指定长度的流
Stream<Double> sa = Stream.generate(Math::random).limit(100);
public final Stream<P_OUT> limit(long maxSize) {
if (maxSize < 0)
throw new IllegalArgumentException(Long.toString(maxSize));
return SliceOps.makeRef(this, 0, maxSize);
}
Stream< T > skip(long n); 丢弃前面的n个元素
2.5 有状态的流转换
无状态的流转换:指流获取一个元素时,并不依赖于之前的状态。
有状态的转换:流必须记住之前已读取的元素。
Stream<String> uqi = Stream.of("m","m","n").distinct();
//去重
Stream<String> longest = uqi.sorted(Comparator.comparing(String::length).reversed());
//排序
2.6 简单的聚合方法
聚合方法就是终止操作。可以理解成对流的最后一个操作(?)
max(),min(),findAny(),anyMatch()….
值得注意的是,这些方法会返回一个Optional< T >值,它可能会封装返回值,也可能表示没有返回值。在使用java其他方法时,通常会抛出null,有可能会抛出空指针异常。所以Optional类型是一种更好的表示缺少返回值的方式。
2.7 Optional类型
2.7.1 正确使用Optional值
或者接受正确值,或者返回一个替代值。Optional类型可以更加简便地完成。
/** * Return the value if present, otherwise return {@code other}. * * @param other the value to be returned if there is no value present, may * be null * @return the value, if present, otherwise {@code other} */
public T orElse(T other) {
return value != null ? value : other;
}
也有更高级的返回替代值的方法
/**
* Return the value if present, otherwise invoke {@code other} and return
* the result of that invocation.
*
* @param other a {@code Supplier} whose result is returned if no value
* is present
* @return the value if present otherwise the result of {@code other.get()}
* @throws NullPointerException if value is not present and {@code other} is
* null
*/
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
2.7.2 创建可选值
Optional.ofNullable(obj)
2.7.3 使用flatMap来组合可选值函数
2.8 聚合操作
Stream<Integer> values = Stream.of(1,2,3,5);
Optional<Integer> sum = values.reduce((x,y)->x+y);
可以得到所有值的和。
一般来说聚合方法有一个聚合操作op,该聚合会产生v1 op v2 op v3…op vn的值。
该操作应该是联合的,如求和,求积(计算顺序不影响计算结果)
减法就不是一个联合操作
另一种形式:
Integer sum2 = values.reduce(0,(x,y)->x+y);
找到标识符e,使得e op x = x,即可使用另一种形式的聚合操作。
2.9 收集结果
用于查看结果:
Object[] toArray(); //Stream里的元素封装成数组,并返回
针对并行的过程,使用collect方法
HashSet<String> set = uqi.collect(HashSet::new,HashSet::add,HashSet::addAll);
uqi是一个Stream。
三个参数分别为:
1. 一个能创建目标类型实例的方法,例如hash set的构造函数
2. 一个能将元素添加到目标中的方法,例如一个add方法
3. 一个将两个对象整合到一起的方法,例如addAll方法
collect方法可以调用Collector类中的很多方法,很实用
tip:
void forEach(Consumer< ? super T> action);
遍历流元素并执行相关操作,但这是一个终止操作。
如不想终止,可以用peek
2.10 将结果收集到Map中
仍然是使用collect方法,方法中调用Collectors中的toMap方法,toMap返回了一个接口
Map<Integer, String> idToName = people.collect(Collenctor.toMap(Person::getId, Person::getName));
假设people是一个Stream<Person>对象
也有toCurrentHashMap方法,用来生成一个并发的map
2.11 分组和分片
对具有相同特性的值进行分组,直接使用groupingBy
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String, List<Locale>> lo = locales.collect(Collectors.groupingBy(Locale::getCountry));
List<Locale> swiss = lo.get("CH");
当分类函数是一个predicate函数时,流元素会被分成两组列表。这时,使用partitioningBy更有效率。
java8还提供了其他一些收集器,用来对分组后的元素进行downstream处理。
以下,是不同参数groupingBy最终调用的方法。
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");
A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
downstreamAccumulator.accept(container, t);
};
BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
@SuppressWarnings("unchecked")
Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;
if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
}
else {
@SuppressWarnings("unchecked")
Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
Function<Map<K, A>, M> finisher = intermediate -> {
intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") M castResult = (M) intermediate; return castResult; }; return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID); } }
2.12 原始类型流
Stream API中:IntStream可以存储short,char,byte和boolean类型的值,DoubleStream可以存储float型。
接口中封装了一些求和,求平均,求最大值之类的方法。
2.13 并行流
流使得并行计算变得容易。
parallel()可以将任意的串行流转换为并行流。
Stream<country> countryStream = Stream.of(China).parallel();
需要确保传递给并行流操作的函数是线程安全的。
2.14 函数式接口
总结Stream的适用场景:
- 集合操作超过两个步骤
比如先filter再for each
这时Stream显得优雅简洁,效率也高 - 任务较重,注重效能,希望并发执行
很容易的就隐式利用了多线程技术。非常好的使用时机。 - 函数式编程的编码风格里
Stream的设计初衷之一