Java玩转stream

stream

什么是stream(流)

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
Stream 可以并行化操作,使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
Stream 的另外一大特点是,数据源本身可以是无限的。

stream的构成

通常包括三个基本步骤:
获取一个数据源 --> 数据转换(Intermediate) --> 执行操作获取想要的结果(terminal)
流管道 (Stream Pipeline) 的构成
这有点像linux命令中的管道操作符|

数据源

  • Collection: Collection.stream, Collection.parallelStream()
  • 数组: Arrays.stream(T array)
  • BufferedReader: java.io.BufferedReader.lines()
  • Spliterator: java.util.Spliterator
  • 其他

Intermediate

一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。

Terminal

一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。

short-circuiting

  • 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。如:limit
  • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。如:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny

stream的使用

构造stream的常见方法

// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();

需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:
IntStream、LongStream、DoubleStream。当然我们也可以用 Stream、Stream >、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

Intermediate操作

1.map

作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素
定义: Stream map(Function<? super T, ? extends R> mapper);
说明:函数或lambda入参T及其父类,返回值R及其子类

//转换大写
List<String> output = wordList.stream().
map(String::toUpperCase).
collect(Collectors.toList());

//平方数
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());
2.flatMap

map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。
flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。
定义: Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
说明:函数或者lambda入参是T及其父类,返回值是Stream及其子类

Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());
3.filter

filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
定义:Stream filter(Predicate<? super T> predicate);
说明:入参T及其父类,返回值布尔值。这是一个断言,通过一个参数,进行逻辑判断最终返回boolean

 List<String> output = reader.lines().
 flatMap(line -> Stream.of(line.split(REGEXP))).
 filter(word -> word.length() > 0).
 collect(Collectors.toList()); 
4.sorted

对stream进行排序,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
有两种排序,带参数的和不带参数的。不带参数的是按照natural order进行排序,对于复杂的对象需要实现Comparable接口来使用。带参数的则在参数中定义排序规则。
定义:Stream sorted(Comparator<? super T> comparator);
说明:主要是要实现这样一个方法int compare(T o1, T o2);的Comparator类。两个同类型的方法返回一个比较的数值

List<Person> persons = new ArrayList();
 for (int i = 1; i <= 5; i++) {
 Person person = new Person(i, "name" + i);
 persons.add(person);
 }
List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);
5.distinct

去重。定义有句描述 according to {@link Object#equals(Object)}。也就是说他是通过这个类的equals方法实现的,如果是自定义类需要重写equals方法,否则所有的对象都不会相等。

6.limit/skip

limit返回前面n个元素
skip扔掉前面n个元素

7.peek
Stream<T> peek(Consumer<? super T> action);

peek方法接收一个Consumer的入参。了解lambda表达式的应该明白Consumer的实现类 应该只有一个方法,该方法返回类型为void。
peek接收一个没有返回值的lambda表达式或函数引用,可以做一些输出,外部处理等

terminal操作

1.max/min/count
  • long count(); 统计数量
  • Optional max(Comparator<? super T> comparator); 返回最大的元素,与排序一样需要传实现了比较接口的类
  • Optional min(Comparator<? super T> comparator); 返回最小的元素
2.reduce

上面的3种都可以通过reduce来实现

  • Optional reduce(BinaryOperator accumulator);
  • T reduce(T identity, BinaryOperator accumulator); 通过一个初始值和一个函数进行计算返回一个值
 Integer sum = integers.reduce(0, (a, b) -> a+b);
 Integer sum = integers.reduce(0, Integer::sum);
 // 字符串连接,concat = "ABCD"
 String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
 // 求最小值,minValue = -3.0
 double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
 // 求和,sumValue = 10, 有起始值
 int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
 // 求和,sumValue = 10, 无起始值
 sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
 // 过滤,字符串连接,concat = "ace"
 concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);
3.collect
  • R collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
    说明:supplier - a function that creates a new result container. For a parallel execution, this function may be called multiple times and must return a fresh value each time.
    结果存放容器
    accumulator - an associative, non-interfering, stateless function for incorporating an additional element into a result.
    为结果如何添加到容器的操作
    combiner - an associative, non-interfering, stateless function for combining two values, which must be compatible with the accumulator function
    多个容器的聚合操作
 String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,StringBuilder::append).toString();
 //等价于上面,这样看起来应该更加清晰
 String concat = stringStream.collect(() -> new StringBuilder(),(l, x) -> l.append(x), (r1, r2) -> r1.append(r2)).toString();
 Lists.<Person>newArrayList().stream()
      .collect(() -> new HashMap<Integer,List<Person>>(),
          (h, x) -> {
            List<Person> value = h.getOrDefault(x.getType(), Lists.newArrayList());
            value.add(x);
            h.put(x.getType(), value);
          },
          HashMap::putAll
  );
  • <R,A> R collect(Collector<? super T,A,R> collector)
    这个是上面那种的高级用法。Collector接口是使得collect操作强大的终极武器,对于绝大部分操作可以分解为旗下主要步骤,提供初始容器->加入元素到容器->并发下多容器聚合->对聚合后结果进行操作,同时Collector接口又提供了of静态方法帮助你最大化的定制自己的操作,官方也提供了Collectors这个类封装了大部分的常用收集操作.
    另外CollectorImpl为Collector的实现类,因为接口不可实例化,这里主要完成实例化操作.
    //初始容器
    Supplier<A> supplier();
    //加入到容器操作
    BiConsumer<A, T> accumulator();
    //多容器聚合操作
    BinaryOperator<A> combiner();
    //聚合后的结果操作
    Function<A, R> finisher();
    //操作中便于优化的状态字段
    Set<Characteristics> characteristics();
Collectors
  • toList()/toSet()/toCollection()
    创建容器: ArrayList::new
    加入容器操作: List::add
    多容器合并: left.addAll(right); return left;
    聚合后的结果操作: 这里直接返回,因此无该操作,默认为castingIdentity()
    优化操作状态字段: CH_ID
    这样看起来很简单,那么对于toCollection,toSet等操作都是类似的实现.
public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }
  • toMap
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,  Function<? super T, ? extends U> valueMapper)
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction)
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)                                    

实际上是4个参数,另外两种是有默认值的。
参数1:keyMapper, 生成键的函数引用或者lambda
参数2:valueMapper,生成值的函数引用或者lambda
参数3:mergeFunction,a merge function, used to resolve collisions between values associated with the same key.对于重复key的处理函数,如果不传则出现重复值时会抛异常
参数4:mapSupplier,a function which returns a new, empty into which the results will be inserted.放最终结果的容器,默认值HashMap::new

  • joining
    合并连接字符串,可加分隔符、前缀、后缀

  • groupingBy
    groupingBy直译过来是分组,可以说它是toMap的一种高级方式,弥补了toMap对值无法提供多元化的收集操作,比如对于返回Map<T,List>这样的形式toMap就不是那么顺手,那么groupingBy的重点就是对Key和Value值的处理封装

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

参数1:classifier,a classifier function mapping input elements to keys.对key值的处理,必传
参数2:mapFactory,a function which, when called, produces a new empty {@code Map} of the desired type.指定Map的容器具体类型,默认值HashMap::new
参数3:downstream,a {@code Collector} implementing the downstream reduction.对Value的收集操作,默认值toList()

Lists.<Person>newArrayList().stream()
        .collect(Collectors.groupingBy(Person::getType, HashMap::new, Collectors.toList()));
//因为对值有了操作,因此我可以更加灵活的对值进行转换
Lists.<Person>newArrayList().stream()
        .collect(Collectors.groupingBy(Person::getType, HashMap::new, Collectors.mapping(Person::getName,Collectors.toSet())));
  • toConcurrentMap
    toMap的并非版本。区别在于 toMap 将创建多个中间结果,然后将合并在一起(此类收集器的供应商将被多次调用),而 toConcurrentMap 将创建单个结果,每个线程都会向其投掷结果(此类收集器的供应商只会被调用一次)。
    toMap 将通过合并多个中间结果在遭遇顺序中在结果Map中插入值(多次调用该收集器的供应商以及组合器)
    toConcurrentMap 将通过抛出所有元素以任何顺序(未定义)收集元素在公共结果容器(在这种情况下为ConcurrentHashMap)。供应商只召集一次,Accumulator多次召唤,而Combiner永远不召唤。

  • reducing
    它是针对单个值的收集,其返回结果不是集合家族的类型,而是单一的实体类T

<T, U> Collector<T, ?, U> reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op)

参数1:identify,U类型的初始值,当不传时会用包装类包装T,并给一T值为null的初始值包装类
参数2:mapper,对元素值T进行一个转换映射成U类型,当T和U是同类型不需要映射时可不传
参数3:op,二元运算,对2个U类型的值计算得到一个U类型的返回值

public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {
        return new CollectorImpl<>(
                boxSupplier(identity),//创建容器
                (a, t) -> { a[0] = op.apply(a[0], t); },//加入容器
                (a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },//动容器合并
                a -> a[0],//聚合后的结果操作
                CH_NOID);//优化操作状态字段
    }
//reducing操作
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
        .stream()
        .collect(Collectors.reducing(0, Integer::sum));    
//当然Stream也提供了reduce操作
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
        .stream().reduce(0, Integer::sum)
  • averagingDouble/Long/Int
    需要一个mapper函数引用,把对应的元素映射成double/long/int,进行求平均计算

  • summingDouble/Long/Int
    需要一个mapper函数引用,把对应的元素映射成double/long/int,进行求和计算

  • counting/maxBy/minBy
    与stream的count/max/min方法效果一样

  • summarizingDouble/Long/Int
    返回一个包含分析计算全部结果的类(count,sum,max,min,avg=sum/count)

  • mapping
    感觉跟steam的map效果差不多

  • collectingAndThen
    Adapts a {@code Collector} to perform an additional finishing transformation.再追加一步操作

List<String> people = people.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
  • partitioningBy
public static <T, D, A>
    Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)

参数1:predicate,断言,执行逻辑返回bool值
参数2:downstream,对value的收集容器,可不传,默认值toList()
这个方法跟groupingBy很相似,但它只能把数据分成两组,true和false

4.forEach
void forEach(Consumer<? super T> action);

对每个元素执行一系列操作,主要是为了改变原值,影响原list。

5.findFirst

返回第一个元素或者空(short-circuiting)

6.findAny

对于串行流结果跟findFirst一样,对于并行流返回的是最快处理完的那个线程的数据(short-circuiting)

7.allMatch/anyMatch/noneMatch
  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
8.toArray

返回数组return an array containing the elements of this stream

参考文档:

  1. https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
  2. https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#map-java.util.function.Function-
  3. https://blog.csdn.net/u012706811/article/details/78058291
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值