当你处理集合时,通常会迭代所有元素并对其中的每一个进行处理。例如,假设我们希望统计一个文件中的所有长单词:
1package java8test; 2 3import java.io.IOException; 4import java.nio.charset.StandardCharsets; 5import java.nio.file.Files; 6import java.nio.file.Paths; 7import java.util.Arrays; 8import java.util.List; 9 10public class T5 { 11 public static void main(String[] args) { 12 try { 13 String contents = new String(Files.readAllBytes( 14 Paths.get("/home/fuhd/work/workspace/javaee/wwos.platform/pom.xml")), 15 StandardCharsets.UTF_8); 16 List<String> words = Arrays.asList(contents.split("\n")); 17 //进行迭代 18 int count = 0; 19 for(String w: words){ 20 if(w.length() > 12) count++; 21 } 22 System.out.println(count); 23 } catch (IOException e) { 24 e.printStackTrace(); 25 } 26 } 27}
这里面有什么错误吗?其实没有——只是它很难被并行计算。这也是java8引入大量操作符的原因。在Java8中,实现相同功能的操作符如下所示:
1package java8test; 2 3import java.io.IOException; 4import java.nio.charset.StandardCharsets; 5import java.nio.file.Files; 6import java.nio.file.Paths; 7import java.util.Arrays; 8import java.util.List; 9 10public class T5 { 11 public static void main(String[] args) { 12 try { 13 String contents = new String(Files.readAllBytes( 14 Paths.get("/home/fuhd/work/workspace/javaee/wwos.platform/pom.xml")), 15 StandardCharsets.UTF_8); 16 List<String> words = Arrays.asList(contents.split("\n")); 17 //注意这一句 18 long count = words.stream().filter(w -> w.length() > 12).count(); 19 System.out.println(count); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24}
stream方法会为单词列表生成一个Stream。filter方法会返回另一个只包含单词长度大于12的Stream。count方法会将Stream化简为一个结果。
一个Stream表面上看与一个集合很类似,允许你改变和获取数据。但是实际上它与集合是有很大区别的:
-
Stream自己不会存储元素。元素可能被存储在底层的集合中,或者根据需要产生出来。
-
Stream操作符不会改变源对象。相反,它们会返回一个持有结果的新Stream。
-
Stream操作符可能是延迟执行的。这意味着它们会等到需要结果的时候才执行。
许多人发现Stream表达式比循环的可读性更好。此外,它们还很容易进行并行执行。以下是一段如何并行统计长单词的代码:
1package java8test; 2 3import java.io.IOException; 4import java.nio.charset.StandardCharsets; 5import java.nio.file.Files; 6import java.nio.file.Paths; 7import java.util.Arrays; 8import java.util.List; 9 10public class T5 { 11 public static void main(String[] args) { 12 try { 13 String contents = new String(Files.readAllBytes( 14 Paths.get("/home/fuhd/work/workspace/javaee/wwos.platform/pom.xml")), 15 StandardCharsets.UTF_8); 16 List<String> words = Arrays.asList(contents.split("\n")); 17 //注意这一句,stream()改成了parallelStream()方法 18 long count = words.parallelStream().filter(w -> w.length() > 12).count(); 19 System.out.println(count); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24}
只要将stream()方法改成parallelStream方法,就可以让Stream API并行执行过滤和统计操作。
Stream遵循“做什么,而不是怎么去做”的原则。在我们的示例中,描述了需要做什么:获得长单词并对它们的个数进行统计。我们没有指定按照什么顺序,或者在哪个线程中做,它们都是理所应当发生的。相反,循环在一开始就需要指定如何进行计算,因此就失去了优化的机会。
当你使用Stream时,你会通过三个阶段来建立一个操作流水线:
-
创建一个Stream。
-
在一个或多个步骤中,指定将初始Stream转换为另一个Stream的中间操作。
-
使用一个终止操作来产生一个结果。该操作会强制它之前的延迟操作立即执行。在这之后,该Stream就不会再被使用
在我们的示例中,通过stream或者parallelStream方法来创建Stream,再通过filter方法对其进行转换,而count就是终止操作
注意:Stream操作不会按照元素的调用顺序执行。在我们的例子中,只有在count被调用的时候才会执行Stream操作。当count方法需要第一个元素时,filter方法会开始请求各个元素,直到找到一个长度大于12的元素。