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

Stream

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

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

collect方法

 R collect(Supplier supplier,
                BiConsumer accumulator,
                BiConsumer 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方法来组装:

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

// 方法引用写法
List 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 >
    Collector toCollection(Supplier collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection::add,
                                   (r1, r2) -> { r1.addAll(r2); return r1; },
                                   CH_ID);
    }

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

实际使用场景:

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

FlapMap

与Map差别:

  • map : 集合中的元素是什么类型,就返回什么类型的流
  • Flapmap:集合流中如果包含了子集合,那么就返回子集合中元素类型的流
Stream> listStream = Stream.of(Arrays.asList("hello1", "world1", "test1")
                , Arrays.asList("hello2", "world2", "test3")
                , Arrays.asList("hello3", "world3", "test3"));
        Stream stringStream = listStream.flatMap(i -> i.stream());
        List list = stringStream.collect(Collectors.toList());
        list.stream().forEach(System.out::println);
List 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 打平为 Stream

findFirst

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

Stream stringStream = Stream.gennerate("abc"::new);
Optional 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 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 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 list = Arrays.asList(student1,student2,student3,student4);

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

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

如果只需要统计分组后各个依据T.xx的个数,只需要将Collectors.groupingBy(T::xx) 替换为Collectors.groupingBy(T::xx, Collectors.counting())即可返回Map

分区

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

小结

  • Stream没有内部存储,只是通过操作管道从source(数据结构,数组,IO等)抓取数据。
  • Stream绝不修改自己所封装的底层数据结构的数据,每次操作都返回一个新的Stream
  • 如果遇到返回的类型的容器或数组,则可以通过flapmap方法来将其扁平化。
  • Stream是支持并行化
  • Stream是可以无限的,通过iterate方法可以无限生成。
  • Stream是可短路的。

你可能感兴趣的:(java,jdk8,stream,lambda,后端)