Java 高级特性 Stream(1)

  流提供了一种让我们可以比集合更高级别上指定计算的数据视图。通过使用流,我们可以说明想要完成什么任务,而不是说明如何去实现他。我们将操作的调度留给具体实现去解决。例如,假设我们想要计算某个属性的平均值,那么我们就可以指定数据源和该属性,然后流库就可以对计算进行优化,例如,使用多线程来计算总数和个数,并将结果合并。

  在本章中,你讲学习如何使用java的流库,他是在java se8中引用的,他用来“做什么而不是怎么做”的方式处理集合。


1.1 从迭代到流的操作

  在处理集合是,我们通常会迭代遍历它的元素,并在每个元素上执行某项操作。例如,假设我们想要对某本书中所有的长单词进行技术。首先,将将所有的单词放到一个列表中:

//Read file into string
String contents=new String(Files.readAllBytes(Paths.get("alice.txt")),StandardCharsets.UTF-8);
//Split into words;nonletters are delimiters
List<String> words=Arrays.asList(contents.split("\\PL+"));

  现在我们可以迭代它了:

 long count=0;
 for(String w:words){
     if(w.length()>12) count++;
 }
 

  在使用流时,相同的操作看起来像下面这样:

 long count=words.stream().filter(w->w.length()>12).count();
 

  流的版本比循环版本要更易于阅读。因为我们不必去扫描整个代码去查找过滤和计数操作,方法名就可以直接告诉我们其代码意欲何为。而且,循环需要非常详细的指定操作的顺序,而流却能够以其想要的任何方式来调度这些操作,只要结果是正确的即可。

  仅将stream修改为parallelStream就可以让流库以并行方式来执行过滤和计数。

 long count=words.parallelStream().filter(w->w.length()>12).count();
 

  流遵循了“做什么而不是怎么做”的原则。在流的示例中,我们描述了需要做什么:获取长单词,并对他计数。我们没有指定该操作应该以什么顺序或者在哪个线程中执行。相比之下,本节开头处的循环要明确的指定计算应该如何工作,因此也就丧失了进行优化的机会。

流表面上看起来和集合很相似,都可以让我们转换和获取数据。但是,他们之间存在着显著的差异:
1. 流并不存储其元素。这些元素可能存储在底层的集合中,或者按需生成。
2. 流的操作不会修改数据源。例如,filter方法不会从新的流中移除元素,而是会生成新的流,其中不包含呗过滤掉的元素。
3. 流的操作是尽可能惰性操作的。这意味着直至需要其结果时,操作才会执行。例如,我们只想查找前五个单词而不是所有长单词,那么filter方法就会匹配到第五个元素时停止过滤。因此我们甚至可以操作无限流。

操作流时的典型流程。我们建立了一个包含三个阶段的操作管道:
1. 创建一个流。
2. 指定将指定流转换为其他流的中间操作,可能包含多个步骤。
3. 应用种植操作,从而产生结果。这个操作会强制执行之前的惰性操作。从此之后,这个流就再也不能用了。

1.2流的创建

  我们可以用Collection接口的stream方法将任何一个集合转换为一个流。如果你有一个数组,那么可以使用静态的Stream.of方法。

String<String> words=Stream.of(contents.split("\\PL+"));

  of方法具有可变长参数,因此我们可以构建具有任何数量引元的流:

String<String> words=Stream.of("a","b","c");

  使用Array.stream(array,from,to) 可以从数组中位于form(包括)和to(不包含)的元素中创建一个流。为了创建不包含任何元素的流可以使用静态的Stream.empty方法。

  Stream接口有两个用户创建无限流的静态方法。generate方法会接受一个不包含任何引元的函数(或者从技术上讲,是一个Supplier接口的对象)。无论何时只需要一个流类型的值,该函数就会被调用以产生一个这样的值。我们可以像下面这样获得一个常量值的流:

Stream<String> echos=Stream.generate(()-> "Echo");

  或者像下面这样获取一个随机数的流:

Stream<Double> randoms=Stream.generate(Math::random);

  为了产生无限序列,例如0,1,2,3… ,可以使用iterate方法。他会接受一个种子值,以及一个函数(从技术上讲就是一个UnaryOperation),并且会反复的将该函数应用与之前的结果上。例如:

Stream<BigInteger> integers=Stream.iterate(BigInteger.ZERO,n->n.add(BigInteger.ZERO));

  该序列中的第一个元素是种子BIgInteger.ZERO,第二个元素是f(seed) ,即1(作为大整数),下一个元素是f(f(seed)),即2,后续以此类推。

注意:JavaAPI中有大量方法中都可以产生流。例如,Pattern类有一个splitAsStream方法,他会按照某个正则表达式来分割一个CharSequence对象。可以使用下面的语句来将一个字符串分割为一个个的单词。

Stream<Stream> words= Pattren.compile("\\PL+").splitAsStream(contents);

静态的Files.lines方法会返回一个包含了文件中所有行的Stream:

try(Stream<Stream> lines=Files.lines(path)){
    process lines
}

下面的示例程序展示了创建流的各种方式。

package com.saas.test.stream;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 创建流
 */
public class CreatingStreams {
    private static <T> void show(String title, Stream<T> stream) {
        final int SIZE = 10;
        List<T> firstElements = stream.limit(SIZE + 1).collect(Collectors.toList());
        System.out.println(title + ": ");
        for (int i = 0; i < firstElements.size(); i++) {
            if (i > 0) System.out.print(",");
            if (i < SIZE) System.out.print(firstElements.get(i));
            else System.out.print("...");
        }
        System.out.println();
    }

    public static void main(String[] args) throws IOException {
        createStream();
        String path = "/Users/dingpengfei/Downloads/test.txt";

        String contents = new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);

        Stream<String> words = Stream.of(contents.split(",")).map(String::toUpperCase);
        words=words.map(s->s.substring(0,1));
    }

    private static void createStream() throws IOException {
        String path = "/Users/dingpengfei/Downloads/test.txt";

        String contents = new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);

        Stream<String> words = Stream.of(contents.split(","));
        show("words", words);

        Stream<String> song = Stream.of("gently", "down", "the", "stream");
        show("song", song);

        Stream<String> silence = Stream.empty();
        show("silence", silence);

        Stream<String> echos = Stream.generate(() -> "Echo");
        show("echos", echos);

        Stream<BigInteger> integerStream = Stream.iterate(BigInteger.ONE, n -> n.add(BigInteger.ONE));
        show("randoms", integerStream);

        Stream<String> wordsAnotherWay = Pattern.compile("\\PL+").splitAsStream(contents);
        show("wordsAnotherWay", wordsAnotherWay);

        try (Stream<String> lines = Files.lines(Paths.get(path), StandardCharsets.UTF_8)) {
            show("lines", lines);
        }
    }
}

1.3 filter map flatMap方法

  流的转换会产生一个新的流,他的元素派生于另一个流中的元素。我们已经看到了filter转换会产生一个流,他的元素与某种条件相匹配。下面,我们将一个字符串流转换为了只包含长单词的另一个流。

List<String> wordList= ...;
Stream<String> longwords=wordList.stream().filter(w->w.length()>12);

  filter的引元是Predicate,即从T到boolean的函数。

  通常我们想要按照某种方式转换流中的值,此时,可以使用map方法并传递执行该转换的函数。例如,我们可以想下面这样将所有单词转换为小写:

List<String> wordList= ...;
Stream<String> longwords=wordList.stream().map(String::toLowerCase);

这里我们使用的是带有方法引用的map,但是,通常我们可以使用lambda表达式来代替:

Stream<String> firstLetters=wordList.stream().map(s-> s.substring(0,1));

上面语句所产生的流包含了所有单词的首字母。

在使用map时,会有一个函数应用于每个元素上,并且其结果是包含了应用该函数后所产生的所有结果的流。现在,假设我们有一个函数,他返回的不是一个值,而是一个包含众多值的流:

public static Stream<String> letters(String s){
    List<String> result=new ArrayList();
    for(int i=0;i<s.length();i++){
        result.add(s.substring(i,i+1));
    }
    return result.stream();
}

例如,letters(“boat”)的返回值是流[“b”,”o”,”a”,”t”]。
通过IntStream.range方法,我们实现这个方法可以优雅的多。假设我们在一个字符串上映射letters方法:

Stream<Stream<String> result=words.stream().map(w->letters(w));

那么就会得到一个包含流的流,就像[…[“h”,”e”,”l”,”l”],[“b”,”o”,”a”,”t”],…]。为了将其摊平为字母流,可以使用flatmap方法而不是map方法:

Stream<String> flatResult=words.stream.flatmap(w ->letters(w));

猜你喜欢

转载自blog.csdn.net/dingpf1209/article/details/81507970