当你处理集合/数组时,通常会迭代所有的元素并对其中的每一个进行处理。例如,统计字符串中“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() 就是终止操作。