前言:流提供了一种让我们可以在比集合更高的概念级别上指定计算的数据视图。如:
//使用foreach迭代 long count = 0; for (String w : words) { if (w.length () > 6) count++; } //使用流 long count = words.stream () .filter (w -> w.length () > 6 ) .count ();
流的版本更易于阅读,流遵循了“做什么而非怎么做”的原则。
一、什么是流
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
Stream 的另外一大特点是,数据源本身可以是无限的。
流表面上和集合很类似,都可以让我们转换和获取数据,但它们之间存在显著的差异:
1、流并不存储其元素。这些元素可能存储在底层的集合中,或者按需生成的。
2、流的操作不会修改其数据源,例如,filter方法不会从新的流中移除元素,而是会生成一个新的流,其中不包含过滤掉的元素。
3、流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。
二、流的构成
如Demo01所示,这个工作流是操作流时的典型流程,我们建立了一个包含三个阶段的操作管道:
1、创建一个流;
2、指定初始流转换为其他流的中间操作,可能包含多个步骤;
3、应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作,从此之后这个流就再也不能用了。
1 package chapter01; 2 import java.io.IOException; 3 import java.nio.charset.StandardCharsets; 4 import java.nio.file.Files; 5 import java.nio.file.Paths; 6 import java.util.Arrays; 7 import java.util.List; 8 9 public class Demo01 { 10 public static void main(String[] args) throws IOException { 11 String contents = new String (Files.readAllBytes ( 12 Paths.get ("alice.txt")), StandardCharsets.UTF_8); 13 List<String> words = Arrays.asList (contents.split ("\\PL+"));//以非字母为分隔符 14 15 long count = 0; 16 for (String w : words) { 17 if (w.length () > 10) count++; 18 } 19 System.out.println (count); 20 21 /*long count = words.stream () 22 .filter (w -> w.length () > 6 ) 23 .count (); 24 System.out.println (count);*/ 25 long count1 = words.parallelStream () 26 .filter (w -> w.length () > 10 ) 27 .count (); 28 System.out.println (count1); 29 } 30 }
三、流的操作
流的操作类型分为两种:
- Intermediate(转换操作):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- Terminal(终止操作):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
四、流的创建
- 通过集合类中的
stream()
、parallelStream()的
方法创建; - 通过数组的
Arrays.stream(Object[])创建
; - 静态工厂方法的流类,如
Stream.of(Object[])
、IntStream.range(int, int)
或Stream.iterate(Object, UnaryOperator)
; - 可以从
BufferedReader.lines()创建
; - 可以通过Files类中的流文件路径方法创建;
- 流的随机数字可以从
Random.ints()创建
; - 许多其他stream-bearing JDK中的方法,包括
BitSet.stream()、
Pattern.splitAsStream(java.lang.CharSequence)、
JarFile.stream()
。
更多的流源可以使用这些技术的第三方库提供。
1 package chapter01; 2 3 import java.io.IOException; 4 import java.math.BigInteger; 5 import java.nio.charset.StandardCharsets; 6 import java.nio.file.Files; 7 import java.nio.file.Path; 8 import java.nio.file.Paths; 9 import java.util.Arrays; 10 import java.util.List; 11 import java.util.regex.Pattern; 12 import java.util.stream.Collectors; 13 import java.util.stream.Stream; 14 15 public class CreatingStreams { 16 public static <T> void show(String title, Stream<T> stream){ 17 final int SIZE = 10; 18 List<T> firstElements = stream 19 .limit (SIZE+1) 20 .collect(Collectors.toList()); 21 System.out.print (title + ":"); 22 for (int i = 0; i < firstElements.size (); i++) { 23 if (i > 0) System.out.print (","); 24 if (i < SIZE ) System.out.print (firstElements.get (i)); 25 else System.out.print ("..."); 26 } 27 System.out.println (); 28 } 29 public static void main(String[] args) throws IOException { 30 Path path = Paths.get ("alice.txt"); 31 String contents = new String (Files.readAllBytes (path), 32 StandardCharsets.UTF_8); 33 34 /** 35 * static <T> Stream<T> of(T... values) 36 * 产生一个元素为指定值的流 37 */ 38 Stream<String> words = Stream.of (contents.split ("\\PL+"));//以标点符号为分隔符 39 show ("words",words); 40 //words:Now,you,don,t,have,to,scan,the,loop,for,... 41 Stream<String> song = Stream.of ("gently","down","the","stream"); 42 show ("song",song); 43 //song:gently,down,the,stream 44 45 /** 46 * static <T> Stream<T> empty() 47 * 产生一个不包含任何元素的流 48 */ 49 Stream<String> silence = Stream.empty (); 50 show ("silence",silence); 51 //silence: 52 53 /** 54 * static <T> Stream<T> generate(Supplier<T> s) 55 * 产生一个无限流,它的值是通过反复调用函数s而构建的 56 */ 57 Stream<String> echos = Stream.generate (() -> "Echo"); 58 show ("echos",echos); 59 //echos:Echo,Echo,Echo,Echo,Echo,Echo,Echo,Echo,Echo,Echo,... 60 61 Stream<Double> randoms = Stream.generate (Math::random); 62 show ("randoms",randoms); 63 //randoms:0.2080635816548484,0.6166016438525503,0.16543339415969027, 64 // 0.3650300855876488,0.9934670157259281,0.6335908473779187,0.9586911195597475, 65 // 0.2948129537037052,0.06688403354835959,0.958008821429481,... 66 67 /** 68 * static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) 69 * 产生一个无限流,它的元素包含种子、在种子上调用f产生的值、在前一个元素上调用f产生的值,等等 70 */ 71 Stream<BigInteger> integers = Stream.iterate (BigInteger.ONE, 72 n -> n.add (BigInteger.ONE)); 73 show ("integers",integers); 74 //integers:1,2,3,4,5,6,7,8,9,10,... 75 76 /** 77 * static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) 78 * 产生一个流,它的元素是数组中指定范围内的元素构成的。 79 */ 80 String[] strings = {"A","B","C","D","E"}; 81 Stream<String> stringStream = Arrays.stream (strings,0,3); 82 show ("stringStream",stringStream); 83 //stringStream:A,B,C 84 85 /** 86 * Stream<String> spiltAsStream(CharSequence input) 87 * 产生一个流,它的元素是输入中由该模式界定的部分 88 */ 89 Stream<String> wordsAnotherWay = Pattern.compile ("\\PL+").splitAsStream (contents); 90 show ("wordsAnotherWay",wordsAnotherWay); 91 //wordsAnotherWay:Now,you,don,t,have,to,scan,the,loop,for,... 92 93 /** 94 * java.nio.file.Files 7 95 * static Stream<String> lines(Path path) 96 * static Stream<String> lines(Path path,Charset cs) 97 * 产生一个流,它的元素是指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集 98 */ 99 try (Stream<String> lines = Files.lines (path,StandardCharsets.UTF_8)){ 100 show ("lines",lines); 101 //lines:Now you don’t have to scan the loop for evidence of filtering and counting. 102 // ,The method names tell you right away what the code intends to do. 103 // ,Moreover, where the loop prescribes the order of operations in complete detail, 104 // ,a stream is able to schedule the operations any way it wants, as long as the result is correct. 105 // ,Specify intermediate operations for transforming the initial stream into others, possibly in multiple steps. 106 } 107 } 108 }
五、处理流的转换
流的转换会产生一个新的流,它的元素派生自另外一个流中的元素。
1、filter、map、flagMap方法
①Stream<T> filter(Predicate<? super T> predicate)
产生一个流,它包含当前流中所有满足断言条件的元素。如将一个字符串流转换为了只含长单词的另一个流:
List<String> wordList = ...;
Stream<String> longWords = wordList.stream().filter(w -> w.length() > 12);
②<R> Stream<R> map(Function<? super T,? extends R> mapper)
产生一个流,它包含将mapper应用于当前流中所有元素所产生的结果。如:将所有单词都转换为小写:
Stream<String> lowercaseWords = words.stream().map(String::toLowerCase);
③<R> Stream<R> flatMap(Function<? super T,? extends R> mapper)
产生一个流,它通过将mapper应用于当前流中所有元素所产生的结果连接到一起而获得的。(这里的每个结果都是一个流。)
2、抽取子流和连接流
①Stream<T> limit(long maxSize)
产生一个流,其中包含了当前流中最初的maxSize个元素(如果原来的流更短,那么就会在流结束时结束)。这个方法对于裁剪无限流的尺寸会显得特别有用,如:产生一个包含100个随机数的流
Stream<Double> randoms = Stream.generate(Math::random).limit(100);
②Stream<T> skip(long n)
产生一个流,它的元素是当前流中除了前n个元素之外的所有元素。
③static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
产生一个流,它的元素是a的元素后面跟着b的元素。
3、其他的转换流
①Stream<T> distinct()
产生一个流,包含当前流中所有不同的元素。
Stream<String> uniqueWords
= Stream.of ("merrily","merrily","merrily","gently").distinct ();
//Only one "merrily" is retained
② Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
产生一个流,它的元素是当前流中的所有元素按照顺序排列的。第一方法要求元素实现了Comparable的类的实例。
Stream<String> longestFirst =
words.stream().sorted(Comparator.comparing(String::length).reversed());
//长字符排在前
③Stream<T> peek(Consumer<? super T> action)
产生一个流,它与当前流中的元素相同,在获取其中每个元素是,会将其传递给action。如:
Object[] powers = Stream.iterate (1.0,p -> p*2)
.peek (e -> System.out.println ("Fetching" + e))
.limit (20).toArray ();
当实际访问一个元素时,就会打印出一条消息。通过这种方式,你可以验证iterate返回的无限流是被惰性处理的。
对应调试,你可以让peek调用一个你设置了断点的方法。
六、简单约简
约简是一种终结操作(terminal operation),它们会将流约简为可以在程序中使用的非流值。
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
分别产生这个流的最大元素和最小元素,使用由给定比较器定义的排序规则,如果这流为空,会产生一个空的Optional对象。这些操作都是终结操作。
Optional<T> findFirst()
Optional<T> findAny()
分别产生这个流的第一个元素和任意一个元素,如果这流为空,会产生一个空的Optional对象。这些操作都是终结操作。
boolean anyMatch(Predicate<? super T> predicate)
boolean allMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)
分别在这个流中任意元素、所有元素和没有任何元素匹配给定端言时返回true,这些操作都是终结操作。
七、Optional类型
Optional<T> 对象是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象。
1、如何使用Optional值
策略一:在没有任何匹配时,使用某种默认值,可以是空字符串。
T orElse(T other)
如:String result = optionalString.orElse("");//The wrapped string,or "" if none
产生这个Optional的值,或者在该Optional为空时,产生other。
T orElseGet(Supplier<? extends T> other)
如:String result = optionalString.orElseGet(() -> Locale.getDefault().getDisplayName());
//The function is only called when needed
产生这个Optional的值,或者在该Optional为空时,产生调用other的结果。
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
如:String result = optionalString.orElseThrow(IllegalStateException::new);
//Supply a method that yields an exception object
产生这个Optional的值,或者在该Optional为空时,抛出调用exceptionSupplier的结果。
策略二:只有在其存在的情况下才消费该值
void ifPresent(Consumer<? super T> consumer)
如:optionalValue.ifPresent(results::add);
如果该Optional不为空,那么就将它的值传递给consumer。
<U> Optional <U> map(Function<? super T,? extends U> mapper)
如:Optional<Boolean> added = optionalValue.map(results::add);
产生将该Optional的值传递给mapper后的结果,只要这个Optional不为空且结果不为null,否则产生一个空Optional。
2、不适合使用Optional值的方式
T get()
产生这个Option的值,或者在该Optional为空时,抛出一个NoSuchElementException对象。
boolean isPresent()
如果该Optional不为空,则返回true。
3、创建Optional
static <T> Optional<T> of(T value)
static <T> Optional<T> ofNullable(T value)
产生一个具有给定值的Optional。如果value为null,那么第一个方法会抛出一个NullPointerException对象,而第二方法会产生一个空Optional。
static <T> Optional<T> empty()
产生一个空Optional。
public static Optional<Double> inverse(Double x){ return x == 0 ? Optional.empty() : Optional.of(1 / x); }
ofNullable(obj)方法被用来作为可能出现的null值和可选值之间的桥梁。
4、用flatMap来构建Optional值的函数
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)
产生将mapper应用于当前的Optional值所产生的结果,或者在当前Optional为空时,返回一个空Optional。
1 import java.io.IOException; 2 import java.nio.charset.StandardCharsets; 3 import java.nio.file.Files; 4 import java.nio.file.Paths; 5 import java.util.*; 6 7 8 public class OptionalTest { 9 public static void main(String[] args) throws IOException { 10 String contents = new String (Files.readAllBytes ( 11 Paths.get ("alice.txt")), StandardCharsets.UTF_8); 12 List<String> wordList = Arrays.asList (contents.split ("\\PL+")); 13 14 Optional<String> optionalValue = wordList.stream () 15 .filter (s -> s.contains ("filter")) 16 .findFirst (); 17 System.out.println (optionalValue.orElse ("No word")+" contains filter"); 18 //filtering contains filter 19 20 Optional<String> optionalString = Optional.empty (); 21 String result = optionalString.orElse ("N/A"); 22 System.out.println ("result:"+result); 23 //result:N/A 24 result = optionalString.orElseGet (() -> Locale.getDefault ().getDisplayName ()); 25 System.out.println ("result:"+result); 26 //result:English (China) 27 28 try { 29 result = optionalString.orElseThrow (IllegalStateException::new); 30 System.out.println ("result:"+result); 31 } catch (Throwable t){ 32 t.printStackTrace (); 33 } 34 /* java.lang.IllegalStateException 35 at java.base/java.util.Optional.orElseThrow(Optional.java:385) 36 at chapter01.OptionalTest.main(OptionalTest.java:32)*/ 37 38 optionalValue = wordList.stream () 39 .filter (s -> s.contains ("met")) 40 .findFirst (); 41 optionalValue.ifPresent (s -> System.out.println (s+" contains met")); 42 //method contains met 43 Set<String> results = new HashSet<String> (); 44 optionalValue.ifPresent (results::add); 45 Optional<Boolean> added = optionalValue.map (results::add); 46 System.out.println (added); 47 //Optional[false] 48 49 System.out.println (inverse(4.0).flatMap(OptionalTest::squareRoot)); 50 //Optional[0.5] 51 System.out.println (inverse(-1.0).flatMap(OptionalTest::squareRoot)); 52 //Optional.empty 53 System.out.println (inverse(0.0).flatMap(OptionalTest::squareRoot)); 54 //Optional.empty 55 Optional<Double> result2 = Optional.of (-4.0) 56 .flatMap (OptionalTest::inverse).flatMap (OptionalTest::squareRoot); 57 System.out.println (result2); 58 //Optional.empty 59 } 60 61 62 private static Optional<Double> inverse(Double x) { 63 return x == 0 ? Optional.empty () : Optional.of (1 / x ) ; 64 } 65 66 private static Optional<Double> squareRoot(Double x) { 67 return x < 0 ? Optional.empty () : Optional.of (Math.sqrt (x)); 68 } 69 70 }
八、收集结果
1 import java.io.IOException; 2 import java.nio.charset.StandardCharsets; 3 import java.nio.file.Files; 4 import java.nio.file.Paths; 5 import java.util.*; 6 import java.util.stream.Collectors; 7 import java.util.stream.Stream; 8 9 public class CollectingResult { 10 public static Stream<String> noVowels() throws IOException { 11 String contents = new String (Files.readAllBytes ( 12 Paths.get ("alice.txt")), StandardCharsets.UTF_8); 13 List<String> wordList = Arrays.asList (contents.split ("\\PL+")); 14 Stream<String> words = wordList.stream (); 15 return words.map (s -> s.replaceAll ("[aeiouAEIOU]","")); 16 } 17 public static <T> void show(String label,Set<T> set){ 18 System.out.print(label + ":" + set.getClass ().getName ()); 19 System.out.println ("[" 20 + set.stream ().limit (10).map (Object::toString) 21 .collect (Collectors.joining (","))+"]"); 22 } 23 24 public static void main(String[] args) throws IOException { 25 /** 26 * java.util.stream.BaseStream 27 * Iterator<T> iterate() 28 * 产生一个用于获取当前流中各个元素的迭代器。这是一个终结操作。 29 */ 30 Iterator<Integer> iter = Stream.iterate (0,n -> n+1).limit (10) 31 .iterator (); 32 while (iter.hasNext ()) 33 System.out.println (iter.next ()); 34 /** 35 *Object[] toArray() 36 *<A> A[] toArray(IntFunction<A[]> generator) 37 * 产生一个对象数组,或者在将引用A[]::new传递给构造器时,返回一个A类型的数组,这些操作都是终结操作。 38 */ 39 Object[] numbers = Stream.iterate (0, n -> n+1).limit (10).toArray (); 40 System.out.println ("Object array:"+numbers);//Note it‘s an Object[] array 41 42 try{ 43 Integer number = (Integer) numbers[0]; 44 System.out.println ("number:" + number); 45 System.out.println ("The following statement throws an exception:"); 46 Integer[] number2 = (Integer[]) numbers;//Throws exception 47 } catch (ClassCastException ex){ 48 System.out.println (ex); 49 } 50 Integer[] number3 = Stream.iterate (0,n -> n+1).limit (10) 51 .toArray (Integer[]::new); 52 System.out.println ("Integer array:" + number3); //Note it‘s an Integer[] array 53 54 /** 55 * java.util.stream.Stream 56 * <R,A> R collect(Collector<? super T,A,R> collector) 57 * 使用给定的收集器来收集当前流中的元素。Collectors类有用于多种收集器的工厂方法。 58 */ 59 /** 60 * java.util.stream.Collectors 61 * static <T> Collectors<T,?,List<T>> toList() 62 * static <T> Collectors<T,?,Set<T>> toSet() 63 * 产生一个将元素收集到列表或集中的收集器 64 */ 65 Set<String> noVowelSet = noVowels () 66 .collect(Collectors.toSet()); 67 show ("noVowelSet",noVowelSet); 68 /** 69 * static <T,C extends Collection<T>> Collector<T,?,C> toCollection(Supplier<C> collectionFactory) 70 * 产生一个将元素收集到任意集合中的收集器。可以传递一个诸如TreeSet::new的构造器引用。 71 */ 72 TreeSet<String> noVowelTreeSet = noVowels () 73 .collect(Collectors.toCollection(TreeSet::new)); 74 show ("noVowelTreeSet",noVowelSet); 75 /** 76 * static Collector<CharSequence,?,String> joining() 77 * static Collector<CharSequence,?,String> joining(CharSequence delimiter) 78 * static Collector<CharSequence,?,String> joining(CharSequence delimiter, 79 * CharSequence prefix,CharSequence suffix) 80 * 产生一个连接字符串的收集器。分隔符会置于字符串之间,而第一个字符串之前可以有前缀,最后一个字符串之后可以有后缀。 81 * 如果没有指定,那么它们都为空 82 */ 83 String result = noVowels ().limit (10).collect (Collectors.joining ()); 84 System.out.println ("Joining:"+result); 85 //Joining:Nwydnthvtscnthlpfr 86 result = noVowels().limit (10).collect (Collectors.joining (",")); 87 System.out.println ("Joining with commas:" + result); 88 //Joining with commas:Nw,y,dn,t,hv,t,scn,th,lp,fr 89 90 /** 91 * static <T> Collector<T,?,IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) 92 * static <T> Collector<T,?,LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper) 93 * static <T> Collector<T,?,DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) 94 * 产生能够生成(Int|Long|Double)SummaryStatistics 对象的收集,通过它可以获得将mapper应用于每个元素后所产生的结果的个数、 95 * 总和、平均数、最大值、最小值。 96 */ 97 IntSummaryStatistics summary = noVowels ().collect ( 98 Collectors.summarizingInt (String::length)); 99 double averageWordLength = summary.getAverage (); 100 double maxWordLength = summary.getMax (); 101 System.out.println ("Average word length:" +averageWordLength); 102 System.out.println ("Max word length:" + maxWordLength); 103 System.out.println ("forEach:"); 104 105 /** 106 * java.util.stream.Stream 107 * void forEach(Consumer<? super T> action) 108 * 在流的每个元素上调用action。这是一种终结操作。 109 */ 110 noVowels ().limit (10).forEach (System.out::println); 111 } 112 }
九、收集到映射表中、群组和分区
1 import java.util.*;
2 import java.util.function.Function;
3 import java.util.stream.Collectors;
4 import java.util.stream.Stream;
5
6 /**
7 * Created by kong on 24/11/2017.
8 */
9
10 public class CollectingIntoMaps {
11 public static class Person{
12 private int id;
13 private String name;
14
15 public Person(int id, String name) {
16 this.id = id;
17 this.name = name;
18 }
19
20 public int getId() {
21 return id;
22 }
23
24 public String getName() {
25 return name;
26 }
27
28 @Override
29 public String toString() {
30 return "Person{" +
31 "id=" + id +
32 ", name=‘" + name + ‘\‘‘ +
33 ‘}‘;
34 }
35 }
36 public static Stream<Person> people(){
37 return Stream.of (new Person (1001,"Peter"),
38 new Person (1002,"Paul"), new Person (1003,"Mary"));
39 }
40
41 public static void main(String[] args) {
42
43
44 /**
45 *
46 * static <T, K, U> Collector<T, ?, Map<K,U>>
47 * toMap(Function<? super T, ? extends K> keyMapper,
48 * Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction)
49 *
50 * static <T,K,U,M extends ConcurrentMap<K,U>> Collector<T,?,M>
51 * toConcurrentMap(Function<? super T,? extends K> keyMapper,
52 * Function<? super T,? extends U> valueMapper,BinaryOperator<U>
53 * mergeFunction,Supplier<M> mapSupplier)
54 *
55 * 产生一个收集器,它会产生一个映射表或并发映射表。ketMapper和valueMapper函数会应用于每个收集到的元素上,
56 * 从而在所产生的映射表中生成一个键/值项。默认情况下,当两个元素产生相同的键时,会抛出一个IllegalStateException异常。
57 * 你可以提供一个mergeFunction来合并具有相同键的值。默认情况下,其结果是一个HashMap或ConcurrentHashMap。
58 * 你可以提供一个mapSupplier,它会产生所期望的映射表实例。
59 */
60 Map<Integer,String> idToName = people ().collect (
61 Collectors.toMap (Person::getId,Person::getName));
62 System.out.println ("idToName:" + idToName);
63 //idToName:{1001=Peter, 1002=Paul, 1003=Mary}
64
65
66 Map<Integer,Person> idToPerson = people ().collect (
67 Collectors.toMap (Person::getId, Function.identity ()));
68 System.out.println ("idToPerson:" + idToPerson.getClass ().getName () + idToPerson);
69 /*idToPerson:java.util.HashMap{1001=Person{id=1001, name=‘Peter‘},
70 1002=Person{id=1002, name=‘Paul‘}, 1003=Person{id=1003, name=‘Mary‘}}*/
71
72 idToPerson = people ().collect (
73 Collectors.toMap (Person::getId,Function.identity (),(existingValue,newValue) -> {
74 throw new IllegalStateException ();
75 }, TreeMap::new));
76 System.out.println ("idToPerson:" +idToPerson.getClass ().getName () + idToPerson);
77 /*idToPerson:java.util.TreeMap{1001=Person{id=1001, name=‘Peter‘},
78 1002=Person{id=1002, name=‘Paul‘}, 1003=Person{id=1003, name=‘Mary‘}}*/
79
80 Stream<Locale> locales = Stream.of (Locale.getAvailableLocales ());
81 Map<String,String> languageNames = locales.collect (Collectors.toMap (
82 Locale::getDisplayLanguage,l -> l.getDisplayLanguage (l),
83 (existingValue,newValue) -> existingValue));
84 System.out.println ("languageNames:" + languageNames);
85 /*languageNames:{=, Nyankole=Runyankore, Ewondo=ewondo, Lingala=lingála,
86 Vunjo=Kyivunjo, Norwegian Nynorsk=nynorsk,......}*/
87
88 locales = Stream.of (Locale.getAvailableLocales ());
89 Map<String,Set<String>> countryLanguageSets = locales.collect (
90 Collectors.toMap (
91 Locale::getDisplayCountry,l -> Collections.singleton (l.getDisplayLanguage ()),
92 (a,b) -> { //union of a and b
93 Set<String> union = new HashSet<> (a);
94 union.addAll (b);
95 return union;
96 }));
97 System.out.println ("countryLanguageSets:" + countryLanguageSets);
98 /*countryLanguageSets:{=[, Nyankole, Ewondo, Lingala,..., Punjabi],
99 Papua New Guinea=[English],...,Greenland=[Danish, Kalaallisut]}*/
100
101 /**
102 *static <T,K> Collector<T,?,Map<K,List<T>>>
103 * groupingBy(Function<? super T,? extends K> classifier)
104 *static <T,K> Collector<T,?,ConcurrentMap<K,List<T>>>
105 * groupingByConcurrent(Function<? super T,? extends K> classifier)
106 * 生成一个收集器,该收集器生成一个map或并发映射,
107 * 其键是将classifier应用于所有收集的元素的结果,其值是具有相同键的元素构成的一个个列表。
108 *
109 * static <T> Collector<T,?,Map<Boolean,List<T>>>
110 * partitioningBy(Predicate<? super T> predicate)
111 * 生成一个收集器,它生成一个映射表,其键是true/false,其值是实现/不满足断言的元素构成的列表。
112 */
113 locales = Stream.of (Locale.getAvailableLocales ());
114 //将具有相同特性的值群聚成组是非常见的,并且groupingBy方法直接就支持它。
115 Map<String,List<Locale>> countryToLocales = locales.collect (
116 Collectors.groupingBy (Locale::getCountry));
117 List<Locale> swissLocales = countryToLocales.get ("CH");
118 System.out.println (swissLocales);
119 //[gsw_CH, de_CH, pt_CH, fr_CH, rm_CH, it_CH, wae_CH, en_CH]
120
121 /*
122 当分类器函数是一个断言函数(即返回一个布尔值的函数)时,流元素被划分为两个列表:
123 函数返回true的元素和其他运算。在这种情况下,使用分区而不是通过分组是更有效的。
124 例如,我们将所有地区划分为使用英语和其他语言的人两类:
125 */
126 locales = Stream.of (Locale.getAvailableLocales ());
127 Map<Boolean,List<Locale>> englishAndOtherLocales = locales.collect (
128 Collectors.partitioningBy (l -> l.getLanguage ().equals ("en")));
129 List<Locale> englishLocales = englishAndOtherLocales.get (true);
130 System.out.println (englishLocales);
131 //[en_NU, en_MS, en_GG, en_JM, en_ZM, ...,en_VG, en_TC, en_IN]
132 }
133 }
十、下游收集器
groupingBy方法生成一个映射表,它的每个值都是一个列表。如果您想以某种方式处理这些列表,就需要提供一个“下游收集器”。例如,如果你想要获得集而不是而不是列表,那么可以使用前一节中看到的Collector.toSet收集器:
Map<String,Set<Locale>> countryToLocaleSet = locales.collect(
grounpingBy(Locale::getCountry,toSet()));
1 import java.io.IOException;
2 import java.nio.file.Files;
3 import java.nio.file.Paths;
4 import java.util.*;
5 import java.util.stream.Stream;
6
7 import static java.util.stream.Collectors.*;
8
9 public class DownstreamCollectors {
10 public static class City{
11 private String name;
12 private String state;
13 private int population;
14
15 public City(String name, String state, int population) {
16 this.name = name;
17 this.state = state;
18 this.population = population;
19 }
20
21 public String getName() {
22 return name;
23 }
24
25 public String getState() {
26 return state;
27 }
28
29 public int getPopulation() {
30 return population;
31 }
32 }
33
34 public static Stream<City> readCities(String filename) throws IOException {
35 return Files.lines (Paths.get (filename)).map (l -> l.split (","))
36 .map (a -> new City (a[0],a[1],Integer.parseInt (a[2])));
37 }
38
39 public static void main(String[] args) throws IOException{
40 Stream<Locale> locales = Stream.of (Locale.getAvailableLocales ());
41
42 locales = Stream.of (Locale.getAvailableLocales ());
43 Map<String,Set<Locale>> countryToLocaleSet = locales.collect (groupingBy(
44 Locale::getCountry,toSet ()));
45 System.out.println ("countryToLocaleSet: " + countryToLocaleSet);
46 //countryToLocaleSet: {=[, nn, bg, ...,lrc, ses, ce],PR=[es_PR, en_PR], PS=[ar_PS], PT=[pt_PT], PW=[en_PW],...}
47
48 /**
49 * static <T> Collector<T, ?, Long> counting()
50 * 产生一个可以对收集到的元素进行计数的收集器。
51 */
52 locales = Stream.of (Locale.getAvailableLocales ());
53 Map<String,Long> countryToLocaleCounts = locales.collect (groupingBy (
54 Locale::getCountry,counting ()));
55 System.out.println ("countryToLocaleCounts: " +countryToLocaleCounts);
56 //countryToLocaleCounts: {=214, PR=2, PS=1, PT=1, PW=1, PY=1, QA=1, AD=1, ...,AW=1}
57
58 /**
59 * static <T> Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
60 * static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
61 * static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)
62 * 产生一个收集器,对mapper应用到收集到的元素上之后产生的值进行计算总和
63 */
64 Stream<City> cities = readCities ("cities.txt");
65 Map<String, IntSummaryStatistics> stateToCityPopulation = cities.collect (groupingBy (
66 City::getState,summarizingInt (City::getPopulation)));
67 System.out.println ("stateToCityPopulation: " + stateToCityPopulation);
68
69 cities = readCities ("cities.txt");
70 Map<String,IntSummaryStatistics> stateToCityPopulationSummary = cities
71 .collect (groupingBy (
72 City::getState,
73 summarizingInt (City::getPopulation)));
74 System.out.println ("stateToCityPopulationSummary: " + stateToCityPopulationSummary);
75
76
77 /**
78 * static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator)
79 * static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator)
80 * 产生一个收集器,使用comparator指定的排序方法,计数收集到的元素中的最大值和最小值。
81 */
82 cities = readCities ("cities.txt");
83 Map<String,Optional<String>> stateToLongestCityName = cities
84 .collect (groupingBy (
85 City::getState,
86 mapping (City::getName,
87 maxBy (Comparator.comparing (String::length)))));
88 System.out.println ("stateToLongestCityName: " + stateToLongestCityName);
89
90 /**
91 * static <T, U, A, R>Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
92 * Collector<? super U, A, R> downstream)
93 * 产生一个收集器,它产生一个映射表,其键是将mapper应用到收集到的数据上而产生的,
94 * 其值是使用downstream收集器收集到的具有相同键的元素
95 */
96 locales = Stream.of (Locale.getAvailableLocales ());
97 Map<String,Set<String>> countryToLanguages = locales.collect (groupingBy (
98 Locale::getDisplayCountry,mapping (Locale::getDisplayLanguage,toSet ())));
99 System.out.println ("countryToLanguages: " + countryToLanguages);
100 /*countryToLanguages: {=[, Nyankole, Ewondo, Lingala,... ,Bemba, Hungarian, Zarma, Punjabi],
101 Papua New Guinea=[English], Cambodia=[Khmer], Paraguay=[Spanish], Kazakhstan=[Kazakh, Russian],...}*/
102
103
104 cities = readCities ("cities.txt");
105 Map<String,String> stateToCityNames = cities
106 .collect (groupingBy (
107 City::getState,
108 reducing ("",City::getName,(s,t) -> s.length () == 0 ? t : s)));
109
110 cities = readCities ("cities.txt");
111 stateToCityNames = cities.collect (groupingBy (City::getState,
112 mapping (City::getName, joining(","))));
113 System.out.println ("stateToCityNames: " + stateToCityNames);
114 }
115 }
十一、约简操作
reduce方法是一种用于从流中计算某值的通用机制,其最简单的形式接受一个二元函数,并从前两个元素开始持续应用它。如果该函数是求和函数,那么很容易解释这种机制:
List<I nteger> values = ...;
Optional<Integer> sum = values.stream().reduce((x, y) - > x + y);
//reduce((x, y) - > x + y)可以写成reduce(Integer::sum);
在上面的情况中,reduce方法会计算v0+v1+v2+...,其中vi是流中的元素。如果流为空,那该方法会返回一个Optional,因为没有任何有效的结果。
十二、基本类型流
我们都是将整数收集到Stream<Integer>中,将每个整数封装到一个包装器对象中是很低效的。对于其他的基本类型来说,同样的情况也适用于double、float、long、short、char、byte和boolean。流库拥有特殊类型的IntStream、LongStream和DoubleStream,它们直接存储基本类型值,而不使用包装器。如果您想要存储short、char、byte和boolean,使用一个IntStream,而对于float,则使用DoubleStream。使用方式与Stream相似。
1 import java.io.IOException;
2 import java.nio.charset.StandardCharsets;
3 import java.nio.file.Files;
4 import java.nio.file.Path;
5 import java.nio.file.Paths;
6 import java.util.stream.Collectors;
7 import java.util.stream.IntStream;
8 import java.util.stream.Stream;
9
10 public class PrimitiveTypeStreams {
11 public static void show(String title, IntStream stream){
12 final int SIZE = 10;
13 int[] firstElements = stream.limit (SIZE+1).toArray ();
14 System.out.print (title+":");
15 for (int i = 0; i < firstElements.length; i++){
16 if (i > 0) System.out.print (",");
17 if (i < SIZE) System.out.print (firstElements[i]);
18 else System.out.print ("...");
19 }
20 System.out.println ();
21 }
22
23 public static void main(String[] args) throws IOException {
24 /**
25 * java.util.stream.IntStream
26 * static IntStream range(int startInclusive, int endExclusive)
27 * static IntStream rangeClosed(int startInclusive, int endInclusive)
28 * 产生一个由给定范围内容的整数构成的IntStream
29 *
30 * static IntStream of(int...values)
31 * 产生一个由当前流中的元素构成的数组
32 * int sum()
33 * OptionalDouble average()
34 * OptionalInt max()
35 * OptionalInt min()
36 * IntSummaryStatistics summaryStatistics()
37 * 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果所有四种值的对象。
38 *
39 * Stream<Integer> boxed()
40 * 产生用于当前流中的元素的包装器对象流
41 */
42 IntStream is1 = IntStream.generate (() -> (int)(Math.random()*100));
43 show ("is1",is1);
44 //is1:51,94,77,28,68,43,98,34,21,95,...
45 IntStream is2 = IntStream.range (5,10);
46 show ("is2",is2);
47 //is2:5,6,7,8,9
48 IntStream is3 = IntStream.rangeClosed (5,10);
49 show ("is3",is3);
50 //is3:5,6,7,8,9,10
51
52 Path path = Paths.get ("alice.txt");
53 String contents = new String (Files.readAllBytes (path), StandardCharsets.UTF_8);
54
55 Stream<String> words = Stream.of (contents.split ("\\PL+"));
56 IntStream is4 = words.mapToInt (String::length);
57 show ("is4",is4);
58 //is4:3,3,3,1,4,2,4,3,4,3,...
59
60 String sentence ="\uD835\uDD46 is the set of octonions.";
61 System.out.println (sentence);
62 //?? is the set of octonions.
63 IntStream codes = sentence.codePoints ();
64 System.out.println (codes.mapToObj (c -> String.format ("%X",c)).collect (
65 Collectors.joining ()));
66 //1D5462069732074686520736574206F66206F63746F6E696F6E732E
67
68 Stream<Integer> integers = IntStream.range (0,100).boxed ();
69 IntStream is5 = integers.mapToInt (Integer::intValue);
70 show ("is5",is5);
71 //is5:0,1,2,3,4,5,6,7,8,9,...
72 }
73 }
十三、并行流
流使得并行化批量操作变得很容易。这个过程基本上是自动的,但是您需要遵循一些规则。首先,你必须有一个并行流。你可以用Collection.parallelStream()方法从任何集合中获取一个并行流:
Stream<String> parallelWords = words.parallelStream();
此外,parallel方法可以将任何顺序流转换为并行流。
Stream<String> parallelWords = Stream.of(wordArray).parallel();
只要在终结方法执行时流处于并行模式,所有中间流操作都将被并行化。
默认情况下,来自有序集合(数组和列表)、范围、生成器和迭代器产生的流,或者通过调用Stream.sorted产生的流,都是有序的。它们结果是按照原始元素的顺序积累的,并且是完全可预测的。如果您两次运行相同的操作,您将得到完全相同的结果。
排序并不排斥高效的并行化处理。例如,当计算Stream.map(fun)时,可以将流划分为n段,每个段都是并发处理的。然后,结果会按顺序重新组合。
当排序需求被删除时,一些操作可以更有效地并行化。通过在流中调用unordered方法,表明对排序不感兴趣。
为了使并行流运行良好,需要满足许多条件:
- 数据应该在内存中。必须等待数据到达是非常低效的。
- 流应该可以被高效的分成若干个子部分。由数组或平衡的二叉树支撑的流可以很好地工作,但是Stream.iterate返回的结果不行。
- 流操作应该做大量的工作。如果总工作负载不是很大,那么为建立并行计算付出的代价是没有意义的。
- 流操作不应该阻塞。
换句话说,不要把所有的流都变成并行的流。只有在对已经存在于内存中的数据进行大量的持续计算工作时,才使用并行流。
1 import java.io.IOException;
2 import java.nio.charset.StandardCharsets;
3 import java.nio.file.Files;
4 import java.nio.file.Paths;
5 import java.util.Arrays;
6 import java.util.List;
7 import java.util.Map;
8 import static java.util.stream.Collectors.counting;
9 import static java.util.stream.Collectors.groupingBy;
10 import static java.util.stream.Collectors.groupingByConcurrent;
11
12 public class ParallelStreams {
13 public static void main(String[] args) throws IOException{
14
15 /**
16 * java.util.stream.BaseStream<T,S extends BaseStream<T,S>>
17 * S parallel()
18 * 产生一个与当前流中元素相同的并行流
19 * S unordered()
20 * 产生一个与当前流中元素相同的无序流
21 *
22 * java.util.Collection<E>
23 * Stream<E> parallelStream()
24 * 用当前集合中的元素产生一个并行流。
25 */
26
27 String contents = new String (Files.readAllBytes (
28 Paths.get ("alice.txt")), StandardCharsets.UTF_8);
29 List<String> wordList = Arrays.asList (contents.split ("\\PL+"));
30
31 //Very bad code ahead
32 int[] shotWords = new int[10];
33 wordList.parallelStream ().forEach ( s -> {
34 if (s.length () < 10) shotWords[s.length ()]++;
35 });
36 System.out.println (Arrays.toString (shotWords));
37 //[0, 2, 13, 17, 11, 6, 6, 4, 7, 1]
38
39 //Try again--the result will likely be different(and also wrong)
40 Arrays.fill (shotWords,0);
41 wordList.parallelStream ().forEach (s -> {
42 if (s.length () < 10) shotWords[s.length ()]++;
43 });
44 System.out.println (Arrays.toString (shotWords));
45 //[0, 2, 13, 17, 11, 6, 6, 4, 7, 1]
46
47 //Remedy: Group and count
48 Map<Integer,Long> shortWordCounts = wordList.parallelStream ()
49 .filter (s -> s.length () < 10 )
50 .collect (groupingBy(String::length,counting ()));
51
52 System.out.println (shortWordCounts);
53 //{1=2, 2=13, 3=17, 4=11, 5=6, 6=6, 7=4, 8=7, 9=1}
54
55 //DownStream order not deterministic
56 Map<Integer,List<String>> result = wordList.parallelStream ().collect (
57 groupingByConcurrent (String::length));
58
59 System.out.println (result.get (14));
60 //null
61
62 result = wordList.parallelStream ().collect (
63 groupingByConcurrent (String::length));
64
65 System.out.println (result.get (14));
66 //null
67
68 Map<Integer,Long> wordCounts = wordList.parallelStream ().collect (
69 groupingByConcurrent (String::length,counting ()));
70
71 System.out.println (wordCounts);
72 //{1=2, 2=13, 3=17, 4=11, 5=6, 6=6, 7=4, 8=7, 9=1, 10=4, 12=2}
73
74 }
75 }