JDK8特性深刻学习笔记-Stream(4)

Stream

Stream自己是一个接口,里面的方法大部分都是高阶函数。Stream是一个元素序列,支持一些串行和并行的操做。java

  • Stream由3部分构成数组

    • 源:这个Stream是来自哪里
    • 零个或多个中间操做:对源的操做,会将当前的流转化为另一个流
    • 终止操做
int sum = widgets.stream()
                 .filter(w -> w.getColor() == RED)
                 .mapToInt(w -> w.getWeight())
                 .sum();
  • 流操做的分类数据结构

    • 惰性求值:只有在终止操做被执行时,中间操做才会被执行,例如只有当sum()执行时,中间的filter和mapToInt才会被执行。
    • 及早求值:终止操做就是及早求值。

Stream的建立方式

// 第一种
Stream stream1 = Stream.of("a", "b", "c");
// 第二种
String[] myArray = new String[]{"a", "b", "c"};
Stream stream2 = Stream.of(myArray);
Stream stream3 = Arrays.stream(myArray);
// 第三种,经过集合建立Stream对象
List<String> list = Arrays.asList(myArray);
Stream stream4 = list.stream();

Stream带来的简化

Stream.of(new int[]{1, 2, 3}).forEach(System.out::println);

IntStream.range(3, 8).forEach(System.out::println);

for循环与Stream的对比

题设条件:有一个List<Integer> list,须要对里面的元素进行 * 2,并将全部的元素结果相加返回并发

for循环

int sum = 0;
for(Integer i : list){
    sum+= i * 2;
}
System.out.println(sum);

Stream

System.out.println(list.stream().mapToInt(i -> i * 2).sum());
System.out.println(list.stream().map(i -> i * 2).reduce(0, Integer::sum));

Stream源码

  • 流自己并不存储值,而是经过管道来获取值。
  • 流本质是函数式的,对流的操做会生成一个结果,不过并不会修改底层的数据源。
  • 流的每次操做都会生成一个新的流

经过流来构建数组和List

Stream<String> stream = Stream.of("a", "b", "c");
// lambda表达式方式
String[] stringArray = stream.toArray(i -> new String[i]);
List<String> stringList = stream.collect(Collectors.toList());
// 方法引用方式(构造方法引用)
 String[] stringArray2 = stream.toArray(String[]::new);

collect方法

<R> R collect(Supplier<R> supplier,
                BiConsumer<R, ? super T> accumulator,
                BiConsumer<R, R> combiner);
  • collect方法是一个终止操做(只要返回的不是Stream,就是终止操做)
  • collect方法是一个泛形的,接受3个参数
  • collect方法是一个可变的汇聚操做,是经过更新结果的状态进行合并的,而不是经过替换结果来进行合并
    collect等价于:
R result = supplier.get();
    for (T element : this stream)
        accumulator.accept(result, element);
    return result;

不使用Collectors.toList,而使用collect方法来组装:dom

// lambda写法
List<String> stringList2 = stream.collect(() -> new ArrayList<>(),(biList, item) -> biList.add(item),(list1, list2) -> list1.addAll(list2));

// 方法引用写法
List<String> stringList3 = stream.collect(ArrayList::new, ArrayList::add,ArrayList::addAll);
  • 第一个Supplier构造了ArrayList集合容器
  • 第二个BiConsumer做为累加器,每次执行的时候都将建立一个List,将stream中的元素添加到这个集合当中
  • 第三个BiConsumer做为合并器,语义实际上是将第二步得到的每一个list,添加到最终返回的List当中

这里谈谈我我的的理解:函数

  1. 第一个构造容器,没什么好说的。
  2. 第二个BiConsumer做为累加器,理论上实现了遍历stream内元素的操做。
  3. 那么为何须要第三步来合并呢,应该在第二步add时就能够所有完成了才对。
  4. 因此我猜想第三个BiConsumer做为合并器,使用场景应该是提供是否支持并行操做使用,若是能支持并行操做,将stream内的元素按照某种规则分配,并行添加完成后,在合并在一块儿,能提高遍历的效率。
以上的内容为我的的猜测。

Stream实例剖析

Collectors.toCollection

public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                   (r1, r2) -> { r1.addAll(r2); return r1; },
                                   CH_ID);
    }

toCollection接收一个Supplier函数方法,来构造容器性能

实际使用场景:ui

Stream<String> stream = Stream.of("a", "b", "c","c");
List<String> stringList = stream.collect(Collectors.toList());
LinkedList<String> linkedList = stringList.stream()
                                  .collect(Collectors.toCollection(LinkedList::new));
Set<String> set = stringList.stream().collect(Collectors.toCollection(HashSet::new));

FlapMap

与Map差异:this

  • map : 集合中的元素是什么类型,就返回什么类型的流
  • Flapmap:集合流中若是包含了子集合,那么就返回子集合中元素类型的流
Stream<List<String>> listStream = Stream.of(Arrays.asList("hello1", "world1", "test1")
                , Arrays.asList("hello2", "world2", "test3")
                , Arrays.asList("hello3", "world3", "test3"));
        Stream<String> stringStream = listStream.flatMap(i -> i.stream());
        List<String> list = stringStream.collect(Collectors.toList());
        list.stream().forEach(System.out::println);
List<String> list = Arrays.asList("hello world","welcome world","hello welcome");
list.stream().map(item -> item.split(" ")).flatMap(i -> Arrays.stream(i)).distinct().collect(Collectors.toList()).forEach(System.out::println);

flapmap将Stream<String[]> 打平为 Stream<String>指针

findFirst

findFirst是一个中断式的方法,返回Stream的第一个元素,以Optional<T>的形式返回。

Stream<String> stringStream = Stream.gennerate("abc"::new);
Optional<String> optionalS = stringStream.findFirst();
optionalS.ifPresent(System.out::println);

至于为何findFirst要返回Optional,应该是为了防止空指针异常,有可能Stream内没有元素。

Iterate 与 limt

Stream.iterate(2,i -> i * 2).limit(10).forEach(System.out::println);
  • Iterate是一个迭代器,入参第一个为起始值,第二个是一个Function,会生成一个无限迭代的无限流。
  • limit是一个中间操做,限制流内元素的个数。

Stream陷阱避免

Stream<Integer> stream = Stream.iterate(1, i -> i + 2)
  .limit(6)
  .filter(i -> i > 2);
System.out.println(stream.skip(3));
System.out.println(stream.count());

这段代码将会抛出异常:java.lang.IllegalStateException: stream has already been operated upon or closed

缘由是stream每次中间操做,都会返回一个新的stream,而且一个stream只容许被操做一次。

而在执行System.out.println(stream.count());时,stream这个流已经被执行过stream.skip(3)了。

Stream.iterate(1, i -> (i + 1) % 2)
  .distinct()
  .limit(6)
  .forEach(System.out::println);

Stream.iterate(1, i -> (i + 1) % 2)
  .limit(6)
  .distinct()
  .forEach(System.out::println);

第一段段代码将会无限执行下去,而第二段则不会。

缘由是limit(6)和 distinct()的顺序不一样,distinct后,代码只会返回2个元素,而limit在等待6个元素,因此iterate将无限执行下去,直到能返回6个元素。

流的短路和并发

并行流能提高多少的性能

List<String> list = new ArrayList<>(5000000);
for (int i = 0; i < 5000000; i++) {
  list.add(UUID.randomUUID().toString());
}
System.out.println("开始排序");
long start = System.nanoTime();
list.stream().sorted().count();
long end = System.nanoTime();
long use = TimeUnit.NANOSECONDS.toMillis(end - start);
System.out.println("使用时间:" + use);

这里是经过串行流完成500w个uuid排序,在个人电脑上,串行流使用了4秒,而并行流只须要1秒,提高了将近4倍。

Stream是可短路的,是将全部的配置项,例如distinct,sort,map等中间操做,汇总在一块儿后才执行,而且只要完成要求,后续的元素遍历将再也不执行,例如findFirst,得到到符合条件的一个元素后,后续元素将再也不获得遍历。

Stream 的分组和分区

分组

Student student1 = new Student("A",100,15);
Student student2 = new Student("B",70,20);
Student student3 = new Student("C",80,25);
Student student4 = new Student("A",59,30);

List<Student> list = Arrays.asList(student1,student2,student3,student4);

Map<String, List<Student>> map = list.stream()
.collect(Collectors.groupingBy(Student::getName));

经过Collectors.groupingBy()方法,将Stream<T>的内容经过分组依据 T.xx,返回为 Map<T.xxx, List<T>>

若是只须要统计分组后各个依据T.xx的个数,只须要将Collectors.groupingBy(T::xx) 替换为Collectors.groupingBy(T::xx, Collectors.counting())便可返回Map<T.xxx, Long>

分区

  • 分组:group by:分组将会依照分组依据来分红多种
  • 分区:partition by:而分区则只会分为两种,一种是符合条件,一种是不符合条件(即true和false)。
Map<Boolean, List<Student>> map = list.stream()
                .collect(Collectors.partitioningBy(i -> i.getScore() >= 60));

小结

  • Stream没有内部存储,只是经过操做管道从source(数据结构,数组,IO等)抓取数据。
  • Stream毫不修改本身所封装的底层数据结构的数据,每次操做都返回一个新的Stream
  • 若是遇到返回的类型的容器或数组,则能够经过flapmap方法来将其扁平化。
  • Stream是支持并行化
  • Stream是能够无限的,经过iterate方法能够无限生成。
  • Stream是可短路的。
相关文章
相关标签/搜索