Java8 教程

Java8 从迭代器到 Stream 操作

当你处理集合/数组时,通常会迭代所有的元素并对其中的每一个进行处理。例如,统计字符串中“A”或“a”字符的个数,例如:

package com.hxstrive.jdk8.stream_api;

/**
 * 迭代器
 * @author hxstrive.com
 */
public class StreamDemo {

    public static void main(String[] args) {
        String str = "God gives us evil at the same time, also give us conquer evil weapons.";
        int count = 0;
        // 迭代字符数组
        for(char c : str.toCharArray()) {
            // 判断 A 或 a 字符
            if(c == 'A' || c == 'a') {
                count += 1;
            }
        }
        System.out.println("count=" + count);
    }
    // 输出结果:
    // count=4
}

上面代码有什么错误吗?明确的说,没有 —— 只是它很难被并行运算。这也是 Java8 引入大量操作符的原因。在 Java8 中,实现相同功能的操作符如下所示:

package com.hxstrive.jdk8.stream_api;

import java.util.stream.Stream;

/**
 * 迭代器
 * @author hxstrive.com
 */
public class StreamDemoForJava8 {

    public static void main(String[] args) {
        String str = "God gives us evil at the same time, also give us conquer evil weapons.";
        
        // 将字符数组转换成流
        Stream<Character> charStream = str.chars().mapToObj(c -> (char)c);
        
        // 通过流的 filter 过滤来统计字符串中字母 A 或者 a 出现的次数
        long count = charStream.filter(e -> e == 'A' || e == 'a').count();
        
        System.out.println("count=" + count);
    }
    // 输出结果:
    // count=4
}

上面代码的 str.chars().mapToObj() 方法生成一个 Stream<Character> 流,然后通过 filter() 方法过滤出等于“A”或“a”的字符,最后通过 count() 统计过滤后的字符个数。

Stream 表面上看与集合很类似,允许你改变和获取数据。但是实际上它与集合有很大的区别:

(1)Stream 自己不会存储元素。元素可能被存储在底层的集合中,或者根据需要生产出来。例如:

package com.hxstrive.jdk8.stream_api;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Stream 自己不会存储元素
 * @author hxstrive.com
 */
public class StreamDemo2 {

    public static void main(String[] args) {
        // 旧List
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        System.out.println("list=" + Arrays.toString(list.toArray()));

        // 新的List
        List<String> newList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
        System.out.println("newList=" + Arrays.toString(newList.toArray()));
    }
    // 输出结果:
    // list=[one, two, three]
    // newList=[ONE, TWO, THREE]
}

(2)Stream 操作符不会改变源对象。相反,它们会返回一个持有结果的新 Stream,见上例。

(3)Stream 操作符可能是延迟执行的。这意味着他们会等到需要结果的时候才执行。例如:

package com.hxstrive.jdk8.stream_api;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Stream 自己不会存储元素
 * @author hxstrive.com
 */
public class StreamDemo3 {

    public static void main(String[] args) {
        // 旧List
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        System.out.println("list=" + Arrays.toString(list.toArray()));

        // 迭代集合
        list.stream().peek(e -> {
            System.out.println("peek1 = " + e);
        }).filter(e -> {
            System.out.println("filter1 = " + e);
            return true;
        });

        list.stream().peek(e -> {
            System.out.println("peek2 = " + e);
        }).filter(e -> {
            System.out.println("filter2 = " + e);
            return true;
        }).collect(Collectors.toList());
    }
    //输出结果:
    //list=[one, two, three]
    //peek2 = one
    //filter2 = one
    //peek2 = two
    //filter2 = two
    //peek2 = three
    //filter2 = three
}

上述例子中,第一个 list.stream().peek().filter() 并没有被执行,而第二个却被执行了,这是因为最后的 collect(Collectors.toList()) 终端操作导致流被执行。

通过上面的学习,了解了有关 Stream 的知识。你可能会发现 Stream 表达式比循环的可读性更好。此外,它们还很容易进行并行执行。下面将统计“A”或“a”字符的示例改为并行示例:

package com.hxstrive.jdk8.stream_api;

import java.util.stream.Stream;

/**
 * 迭代器
 * @author hxstrive.com
 */
public class StreamDemo4 {

    public static void main(String[] args) {
        String str = "God gives us evil at the same time, also give us conquer evil weapons.";
        // 将字符数组转换成流
        Stream<Character> charStream = str.chars().mapToObj(c -> (char)c);
        // 通过流的 filter 过滤来统计字符串中字母 A 或者 a 出现的次数
        // 下面的 parallel() 方法是并行处理,如果不加,默认是顺序处理
        long count = charStream.parallel().filter(e -> e == 'A' || e == 'a').count();
        System.out.println("count=" + count);
    }
    // 输出结果:
}

上面示例,仅仅在使用流的时候调用了 parallel() 方法,就可以让 Stream API 并行执行过滤和统计操作。

Stream 遵循“做什么,而不是怎么去做”的原则。在我们的示例中,描述了需要做什么:统计字符串中“A”或“a”字符的个数统计。我们没有指定按照什么顺序,或者在哪个线程中做。相反,循环在一开始就需要指定如何进行计算,因此就失去了优化的机会。

当你使用 Stream 时,你会通过三个阶段来建立一个操作流水线:

(1)创建一个 Stream。

(2)在一个或多个步骤中,指定将初始 Stream 转换为另一个 Stream 的中间操作。

(3)使用一个终止操作来产生一个结果。该操作会强制它之前的延迟操作立即执行。在这之后,该 Stream 就不会再被使用了。

在我们的示例中,通过 stream() 或者 parallel() 方法来创建 Stream,再通过 filter() 方法对其进行转换,而 count() 就是终止操作。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号