java8——Stream API

来源:互联网 发布:利用淘宝客赚钱 编辑:程序博客网 时间:2024/04/27 20:16

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表达式@FunctionalInterfacepublic 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的设计初衷之一
原创粉丝点击