Java8 Stream之浅入

137 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

初识

笔者认为觉得api文档才是最专业说明,所以先翻译下文档。

为了执行计算,流操作被组合成一个流管道。流管道由(可能是数组、集合、生成器函数、I/O通道等)、零个或多个中间操作(将流转换成另一个流,例如filter(Predicate)),和末端操作(产生结果或副作用,例如count()或者forEach(Consumer))。流是懒惰的;仅在发起末端操作时才对原数据进行计算,并且仅在需要时消耗源元素。

集合和流虽然有一些表面上的相似之处,但有不同的目标。集合主要关注其元素的有效管理和访问。相比之下,流不提供直接访问或操作其元素的方法,而是以声明的方式描述其源以及在源上执行的聚合操作。但是,如果提供的流操作不提供所需的功能,则BaseStream.iterator()BaseStream.spliterator()操作可用于执行受控遍历。

一个流应该只被操作一次(调用一个中间或末端流操作)。例如,“分叉”流,其中相同的源提供两个或多个管道,或同一个流的多次遍历。如果流检测到被重用将抛出IllegalStateException。但是,由于某些流操作可能返回其接收者而不是新的流对象,因此可能无法在所有情况下检测重用。

流管道可以顺序或并行执行。这种执行模式时流的属性。流是通过初始选择顺序或并行执行来创建。(例如,Collection.stream()创建一个顺序流,Collection.parallelStream()创建一个并行流。)这种执行模式的选择可以通过 BaseStream.sequential()或者BaseStream.parallel()方法进行修改,并且可以通过该方法进行查询BaseStream.isParallel()

我们从以下几个点去了解下:

  • 为啥要使用Stream
  • 如何创建流
  • 流的中间操作使用
  • 流的末端操作使用
  • 流的并行执行如何使用
    • 并行是怎么实现的
  • Stream的底层是怎么实现的
    • 流的惰性执行是怎么实现的

体验一下

从给定句子中返回单词长度大于5的单词列表,按长度倒序输出,最多返回3个

  • Java7及以前
public static List<String> sortGetTop3LongWords(String sentence) {
    String[] words = sentence.split(" ");
    List<String> wordList = new ArrayList<>();
    for (String word : words) {
        if (word.length() >= 5) {
            wordList.add(word);
        }
    }

    wordList.sort(((o1, o2) -> o2.length() - o1.length()));
    if (wordList.size() > 3) {
        wordList = wordList.subList(0, 3);
    }
    return wordList;
}
  • Java8及以后
public static List<String> sortGetTop3LongWordsByStream(String sentence) {
    return Arrays.stream(sentence.split(" ")).filter(word -> word.length() > 5)
            .sorted((o1, o2) -> o2.length() - o1.length()).limit(3).collect(Collectors.toList());
}

第一感受就是变简洁了许多,而且如果对Stream API稍有了解的话,笔者认为易读性更好。

如何创建流

Collection

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();//顺序流
Stream<String> stringStream = list.parallelStream();//并行流

数组转成流

Stream<Integer> integerStream = Arrays.stream(new Integer[]{1, 2, 3, 4});

Stream中的静态方法

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4);
// iterate()、generate()可以创建无限流,可以通过limit()方法限制数量
Stream<Integer> stream2 = Stream.generate(() -> new Random().nextInt(10)).limit(10);
Stream<Integer> stream3 = Stream.iterate(0, x -> x + 2).limit(10);

BufferedReader.lines()方法

Stream<String> linesStream = new BufferedReader(new FileReader("/Users/xxx/Downloads/test")).lines();

Pattern.splitAsStream()方法

Pattern pattern=Pattern.compile(",");
Stream<String> stringStream1 = pattern.splitAsStream("a,b,c,d");

流的中间操作使用

filter

Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
integerStream.filter(i -> i % 2 == 0);
integerStream.forEach(System.out::println);

这里会报错:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at com.study.stream.IntermediateOperations.main(IntermediateOperations.java:9)

改下:

Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> streamFilter = integerStream.filter(i -> i % 2 == 0);
streamFilter.forEach(System.out::println);

原因是因为流只能被操作一次,上面“初识”章节中也提到了。

skip & limit

  • skip(n):跳过n元素,配合limit(n)可实现分页。
  • limit(n):限制获取元素的个数。
Stream<Integer> integerStream1 = Stream.of(1, 2, 3, 4, 5, 6);
integerStream1.skip(3).limit(3).forEach(integer -> System.out.print(integer + " "));

distinct

去重

Stream<Integer> integerStream2 = Stream.of(1, 2, 3, 6, 6, 6);
integerStream2.distinct().forEach(integer -> System.out.print(integer + " "));

map

逐个对每个元素进行映射成新的元素。

Stream<Integer> integerStream3 = Stream.of(1, 2, 3, 4, 5, 6);
integerStream3.map(integer -> integer+1).forEach(integer -> System.out.print(integer + " "));
System.out.println();

flatmap

平铺映射,逐个将每个元素拆成stream,最终合成一个stream。

Stream<List<Integer>> listStream = Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6));
listStream.flatMap(list-> list.stream()).forEach(integer -> System.out.print(integer + " "));

sorted

可以调用sorted(Ljava/util/Comparator)Ljava/util/stream/Stream实现Comparator匿名内部类,如果流中的元素实现了Comparable接口也可以直接调用调用sorted()。

public static void main(String[] args) {
    Stream<Person> listStream1 = Stream.of(new Person("wf", 1), new Person("fw", 3), new Person("fw", 2));
    listStream1.sorted(((o1, o2) -> {
        if (o1.name.equals(o2.name)) {
            return o1.age - o2.age;
        } else {
            return o1.name.compareTo(o2.name);
        }
    })).forEach(System.out::println);
}

@AllArgsConstructor
@ToString
static class Person {
    private String name;
    private Integer age;
}

peek

和map的不同在于map是Function<? super T, ? extends R> mapper有返回,peek是Consumer<? super T> action没有返回。

Stream<Integer> integerStream4 = Stream.of(1, 2, 3, 4, 5, 6);
integerStream4.peek(System.out::println).collect(Collectors.toList());

注意因为peek是中间操作,所以直接peek不会执行。

流的末端操作使用

match

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().allMatch(integer -> integer > 3));
System.out.println(list.stream().noneMatch(integer -> integer > 5));
System.out.println(list.stream().anyMatch(integer -> integer > 3));

输出:

false
true
true

find

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().findFirst().get());
System.out.println(list.stream().findAny().get());

输出:

1
1

count

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().count());

max

System.out.println(list.stream().max(Integer::compareTo).get());

min

System.out.println(list.stream().min(Integer::compareTo).get());

reduce

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().reduce((a, b) -> a + b).get());

输出:

15
  • 给定初始值
System.out.println(list.stream().reduce(100,(a, b) -> a + b));

collect

List<Person> peoples = new ArrayList<Person>() {
    {
        {
            add(new Person("wf", 1));
            add(new Person("fw", 3));
            add(new Person("w", 2));
        }
    }
};
//转成map
System.out.println(peoples.stream().collect(Collectors.toMap(Person::getName, Person::getAge)));

//拼接
System.out.println(peoples.stream().map(Person::getName).collect(Collectors.joining(",")));

//统计
System.out.println(peoples.stream().collect(Collectors.counting()));

//最大
System.out.println(peoples.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compare)).get());

//所有人的年龄求和
System.out.println(peoples.stream().collect(Collectors.summingInt(Person::getAge)));

//平均年龄
System.out.println(peoples.stream().collect(Collectors.averagingInt(Person::getAge)));

IntSummaryStatistics statistics = peoples.stream().collect(Collectors.summarizingInt(Person::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());

//按年龄分组
System.out.println(peoples.stream().collect(Collectors.groupingBy(Person::getAge)));

// 分区 分成两部分,一部分大于2,一部分小于等于2
peoples.stream().collect(Collectors.partitioningBy(p -> p.getAge() > 2));

// reducing sum
System.out.println(peoples.stream().map(Person::getAge).collect(Collectors.reducing(Integer::sum)).get());

参考

玩转Java8 Stream流

一文吃透 JAVA Stream 流操作!