Stream

头像
WinRT
    阅读 5 分钟
    1

    从迭代到Stream操作

    流表面上看起来与集合相似,允许你转换和检索数据。然而,两者却有显著的不同:

    • 流不储存元素。元素储存在底层的集合或者按需生成。
    • 流操作不改变它们的源数据。
    • 如果可能的话,Stream操作就是延迟执行的。这意味着直到需要结果时,方法才会执行。

    Stream遵循"做什么,而不是怎么去做"的原则。
    比如,例子:计算集合中长度大于3的总数

    String str = "aaa,cccc,dddd,eeee,ffff";  
    List<String> words = List.of(str.split(","));  
    //迭代  
    int count = 0;  
    for (String w : words) {  
        if(w.length() > 3) count++;  
    }
    
    //使用流时,只需要描述需要做的事情
    long count = words.stream().filter(w -> w.length() >3).count();
    

    创建Stream

    转换成流

    • 面对集合,使用Collection接口的stream方法
    • 面对数组,使用Stream.of();

    创建一个不包含任何元素的Stream
    Stream<String> silence = Stream.empty()

    创建无限Stream的静态方法

    • 创建一个常量值,使用generate()
    //创建一个常量值
    Stream.generate(() -> "Echo");
    //创建一个随机数
    Stream.generate(Math :: random);
    • 创建一个例如0 1 2 3...... 这样的无穷序列,可使用iterate(),
    Stream.iterate(BigInteger.ZERO , n->n.add(BigInteger.ONE));

    转换流

    filter

    转换成一个匹配一定条件的新流。
    stream().filter(w -> w.length() > 12);

    map

    将一个流中的值进行某种形式的转换。
    比如:将所有单词转换成小写形式

    List<String> words = ...
    Stream<String> lowercaseWords = words.stream().map(String :: toLowerCase);

    flatMap

    public static Stream<String> codePoints(String s){  
        List<String> result = new ArrayList<>();  
        int i =0;  
        while (i < s.length())  
        {  
            int j = s.offsetByCodePoints(i, 1);  
            result.add(s.substring(i,j));  
            i = j;  
        }  
        return result.stream();  
    }
    
    String[] strWords = {"words"};  
    //将得到一个包含多个流的流
    Stream<Stream<String>> result1 = Stream.of(strWords).map(w -> codePoints(w));  
    
    //将得到一个只包含字符串的流
    Stream<String> result2 = Stream.of(strWords).flatMap(w -> codePoints(w));

    提取子流和组合流

    stream.limit()

    返回一个包含n个元素的新流(如果原始流的长度小于n,则会返回原始流)。
    Stream.generate(Math :: random).limit(100);

    stream.skip()

    它会丢弃前n个元素。
    Stream.of("a","b","c").skip(1);

    stream.takeWhile(predicate)

    从流接收所有元素,当predicate为true时,停止接收。

    Stream.of("a","b","c","","e","f").takeWhile(s->!s.isEmpty())  
            .forEach(System.out::print);

    stream.dropWhile()

    当条件判断为true时,丢弃元素。该方法生成一个流,该流包含了所以条件判断为false的元素。

    Stream.of("a","b","c","","e","f").dropWhile(s->s.isEmpty()).forEach(System.out::print);

    Stream.concat()

    可以将两个流连接起来。
    Stream.concat(Stream.of("a","b","c"),Stream.of("e","f")).forEach(System.out::print);

    其他流转换

    distinct()

    去重

    sorted()

    排序
    Stream.of("gg","b","c","","e","f").sorted(Comparator.comparing(String::length).reversed());

    peek()

    生成一个与原先流一样有着相同元素的新流,但是每当检索一个元素时函数就调用一次。对调试来说非常方便。
    如果你想知道流的流水线之特定地方发生了什么,则添加如下代码:

    .peek(x ->{
        return;})
    并在第二行设置断点。

    简单归约

    从流数据中获得答案,归约是终止操作,比如count()、max()、min()等。

    //大部分方法会返回一个Optional<T>类型的值,它可能会封装返回值,也可能表示没有返回(当流为空时)。
    String str = "b,o,a,t";  
    List<String> words = List.of(str.split(","));  
    Optional<String> largest = words.stream().max(String::compareToIgnoreCase);  
    System.out.println(largest.orElse(""));

    Optional类型

    有效的使用Optional的关键在于:使用一个要么如果值不存在,产生另一个替代值; 要么如果值存在,使用该值的方法。

    如果值不存在,产生另一个替代值

    • 使用一个默认值
    String result = optionalString.orElse("");
    • 调用代码来计算默认值
    String result = optionalString.orElseGet(() -> System.getProperty("myapp.default"));
    • 抛出一个异常
    String result = optionalString.orElseThrow(IllegalStateException :: new);

    当值存在,使用它

    • 使用ifPresent(),如果Optional值存在的话,则它会被传递给函数。当使用它给函数传递Option值时,函数返回值会丢失。
    optionalValue.ifPresent(v -> Process v);
    //例如:
     List<String> list = new ArrayList<>();  
     optionalValue.ifPresent(v ->list.add(v));
     //或者
     optionalValue.ifPresent(list::add);
    • ifPresentOrElse(),当Optional有值时执行某个任务,没有值时执行另外一个任务。
    optionalValue.ifPresentOrElse(v -> Process v, ()->执行其他任务);
    • 使用map(),可以处理函数结果。
    //如果存在一个值,则返回一个可选的,描述将给定的映射函数应用于该值的结果(就像通过ofNullable一样),否则返回一个空的可选的。如果映射函数返回空结果,则此方法返回空的可选结果。
    Optional<Boolean> data = optionalValue.map(list::add);

    收集结果

    遍历流
    stream.forEach(System.out :: printIn);

    将结果收集到一个数据结构中

    • 收集到数组中
    String[] result = stream.toArray(String[] :: new);
    //stream.toArray()会返回Object[]类型,可以将类型传递给数组的构造函数,来获取相应类型的数组
    • 收集到另一个目标容器里
    //收集到list中
    List<String> result = stream.collect(Collectors.toList());
    //收集到set中
    Set<String> result = stream.collect(Collectors.toSet());
    //收集到哪种集合中
    HashSet<String> result = stream.collect(Collectors.toCollection(HashSet::new));
    //如果你想将流中的所有字符串拼接并收集起来,并且流包含字符串以外的对象
    String result = stream.map(Object::toString).collect(Collectors.joining());
    //也可以在元素之间插入分隔符
    String result = stream.map(Object::toString).collect(Collectors.joining(","));
    //收集到Map中
    Stream<String> stream = Stream.of("aaa", "bbbb", "ccccc");  
    Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String:: length));
    //数据视图结果:{aaa=3, ccccc=5, bbbb=4}
    //Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如`t -> t`形式的Lambda表达式

    基本类型流

    Stream类库提供了IntStream、LongStream和DoubleStream类型,分别表示原始int流、原始long流和原始double流,而不用进行包装。

    并行流

    使用并行流能够自动将大型数据集上的操作并行化。只有当你对已经存在于内存中的大量数据进行持续计算时,才需要使用并行流,并且要确保远离对共享对象的不安全修改。

    String str = "aaa,cccc,dddd,eeee,ffff";  
    List<String> words = List.of(str.split(","));  
    long count = words.parallelStream().filter(w -> w.length() >3).count();
    
    //如果words对象已经是流,可以使用parallel()并行执行。
    long count = words.parallel().filter(w -> w.length() >3).count();

    WinRT
    24 声望4 粉丝

    临渊羡鱼,不如退而结网