java8——Stream API

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();

使用流水线:

  1. 创建Stream(流) (2.2)
  2. 在一个或多个步骤中,将初始Stream转化为另一个Stream的中间操作,如filter (2.3-2.5)
  3. 终止操作,会强制它之前的延迟操作立即执行。如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的设计初衷之一
    原文作者:qqqqq1993qqqqq
    原文地址: https://blog.csdn.net/qqqqq1993qqqqq/article/details/72859475
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞