1.从迭代到流的操做java
在处理集合时,咱们一般会迭代遍历它的元素,并在每一个元素上执行某项操做。例如,假设咱们想要对某本书的全部长单词进行计数。首先,将全部单词放到一个列表中:正则表达式
String contents=new String(File.readAllBytes( Paths.get("alice.txt")),StandardCharsets.UTF_8);// Read file into string List<String> words=Arrays.asList(contents.split("\\PL+")); // Split into words; nonletters are delimiters
如今,咱们能够迭代它了:数组
long count=0; for(String w: words){ if(w.length()>12) count++; }
在使用流时,相同的操做看起来像下面这样:安全
long count=words.stream() .filter(w -> w.length() >12) .count();
流的版本比循环版本更易于阅读,由于咱们没必要扫描整个代码去查找过滤和计数操做,方法名就能够直接告诉咱们其代码意欲何为。并且,循环须要很是详细地指定操做的顺序,而流却可以以其想要的任何方式来调度这些操做,只要结果是正确的便可。并发
流遵循了“作什么而非怎么作”的原则。在流的示例中,咱们描述了须要作什么:获取长单词,并对它们计数。咱们没有指定该操做应该以什么顺序或者哪一个线程中执行。相比之下,本节开头处的循环要确切指定计算应该如何工做,所以也就丧失了进行优化的机会。app
流表面上看起来和集合很相似,均可以让咱们转换和获取数据。可是,它们之间存在着显著的差别:dom
1)流并不存储其元素。这些元素可能存储在底层的集合中,或者是按需生成的。ide
2)流的操做不会改变其数据源。例如,filter方法不会重新的流中移除元素,而是会生成一个新的流,其中不包括被过滤掉的元素。函数
3)流的操做是尽量地惰性执行的。这意味着直至须要结果时,操做才会执行。例如,若是咱们只想查看前5个长单词而不是全部长单词,那么filter方法就会在匹配到第5个单词后中止过滤。所以,咱们甚至能够操做无限流。测试
stream和paralellelStream方法会产生一个用于words列表的stream。filter方法会返回另外一个流,其中包括长度大于12的单词。count方法会将这个流化简为一个结果。
count=words.parallelStream().filter(w->w.length() >12).count();
这个工做流是操做流时的典型流程。咱们创建了一个包括三个阶段的操做管道:
1)建立一个流。
2)指定将初始流转换为其余流的中间操做,可能包含多个步骤。
3)应用终止操做,从而产生结果。这个操做会强制执行以前的惰性操做。今后以后,这个流就不再能用了。
java.util.stream.Stream<T>:
Stream<T> filter(Predicate<? super T> p)
产生一个流,其中包含当前流中知足P的全部元素。
long count()
产生当前流中元素的数量。这是一个终止操做。
java.util.Collection<E>:
default Stream<E> stream()
default Stream<E> parallelStream()
产生当前集合中全部元素的顺序流或并行流。
2.流的建立
若是你有一个数组,那么可使用静态的Stream.of方法。
Stream<String> words=Stream.of(contents.split("\\PL+")); // split returns a String[] array
of方法具备可变长参数,所以咱们能够构建具备任意数量引元的流:
Stream<String> song=Stream.of("gently","down","the","stream");
使用Array.stream(array,from,to)能够从数组中位于from(包括)和to(不包括)的元素中建立一个流。
为了建立不包含任何元素的流,可使用静态的Stream.empty方法:
Stream<String> silence=Stream.empty(); //Generic trype <String> is inferred; same as Stream.<String>empty()
Stream接口有两个用于建立无限流的静态方法。generate方法会接受一个不包含任何引元的函数(或者从技术上讲,是一个Supplier<T>接口的对象)。不管什么时候,只要须要一个流类型的值,该函数就会被调用以产生这样的值。咱们能够像下面这样得到一个常量值的流:
Stream<String> echos=Stream.generate(() -> "Echo");
或者像下面这样获取一个随机数的流:
Stream<Double> randoms=Stream.generate(Math::random);
为了产生无限序列,例如0 1 2 3 ...,可使用iterate方法。它会接受一个“种子”值,以及一个函数(从技术上讲,是一个UnaryOperation<T>),而且会反复地将该函数应用到以前的结果上。例如,
Stream<BigInteger> integers=Stream.iterate(BigInterger.ZERO,n -> n.add(BigInteger.ONE));
该序列中的第一个元素是种子BigInterger.ZERO,第二个元素是f(seed),即1(所为大整数),下一个元素f(f(seed)),即2,后续以此类推。
注:JavaAPI中有大量方法均可以产生流。例如,Pattern类有一个splitAsStream方法,它会按照某个正则表达式来分割一个CharSequence对象。可使用下面的语句来将一个字符串分割为一个个的单词:
Stream<String> words=Pattern.compile("\\PL+").splitAsStream(contents);
静态的Files.lines方法会返回一个包含了文件中全部行的Stream:
try(Stream<String> lines=Files.lines(path)){ Process lines }
java.util.stream.Stream:
static <T> Stream<T> of(T... values)
产生一个元素为给定值的流。
static <T> Stream<T> empty()
产生一个不包含元素的流。
static <T> Stream<T> generate(Supplier<T> s)
产生一个无限流,它的值是经过反复调用函数s而构建的。
static <T> Stream<T> iterate(T seed,UnaryOperator<T> f)
产生一个无限流,它的元素包含种子,在种子上调用f产生的值,在前一个元素上调用f产生的值,等等。
java.util.Arrays
static <T> Stream<T> stream(T[] array,int startInclusive,int endExclusive)
产生一个流,它的元素是由数组中指定范围内的元素构成的。
java.util.regex.Pattern
Stream<String> splitAsStream(CharSequence input)
产生一个流,它的元素是输入中由该模式界定的部分。
java.nio.file.Files
static Stream<String> lines(Path path)
static Stream<String> lines(Path path,Charset cs)
产生一个流,它的元素是指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集。
java.util.function.Supplier<T>
T get()
提供一个值。
3.filter,map和flatMap方法
流的转换会产生一个新的流,它的元素派生自另外一个流中的元素。
下面,咱们将一个字符串流转换为了只包含长单词的另外一个流:
List<String> wordList=...; Stream<String> longWords=wordList.stream().filter(w -> w.length() >12);
filter的引元是Predicate<T>,即从T到boolean的函数。
一般,咱们想要按照某种方式来转换流中的值,此时,可使用map方法并传递只执行该转换的函数。例如,咱们能够像下面这样将全部单词都转换为小写。
Stream<String> lowercaseWords=words.stream().map(String::toLowerCase);
这里,咱们使用的是带有方法引用的map,可是,一般咱们可使用lambda表达式来代替:
Stream<String> firstLetters=words.stream().map(s -> s.substring(0,1));
上面语句所产生的流中包含了全部单词的首字母。
在使用map时,会有一个函数应用到每一个元素上,而且其结果是包含了应用该函数后所产生的全部结果的流。如今假设咱们有一个函数,它返回的不是一个值,而是一个包含众多值的流:
public static Stream<String> letters(String s){ List<String> result=new ArrayList<>(); for(int i=0;i<s.length();i++) result.add(s.substring(i,i+1)); return result.stream(); }
例如,letters("boat")的返回值是流["b","o","a","t"]。
假设咱们在一个字符串流上映射letters方法:
Stream<Stream<String>> result=words.stream().map(w -> letters(w));
那么就会获得一个包含流的流,就像[...["y","o","u","r"],["b","o","a","t"],...]。
为了将其摊平为字母流[..."y","o","u","r","b","o","a","t",...],可使用flatMap方法而不是map方法:
Stream<String> flatResult=words.stream().flatMap(w ->letters(w)); //Calls letters on each and flattens the results
注:在流以外的库你也会发现flatMap方法,由于它是计算机科学中的一种通用概念。假设咱们有一个泛型G(例如Stream),以及将某种类型T转换为G<U>的函数f和将类型U转换为G<V>的函数g。而后,咱们能够经过使用flatMap来组合它们,即首先应用f,而后应用g。这是单子论的关键概念。可是没必要担忧,咱们无需了解任何有关单子的知识就可使用flatMap。
java.util.stream.Stream:
Stream<T> filter(Predicate<? super T> predicate)
产生一个流,它包含当前流中全部知足断言条件的元素。
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
产生一个流,它包含将mapper应用与当前流中全部元素所产生的结果。
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
产生一个流,它是经过将mapper应用于当前流中全部元素所产生的结果链接到一块儿而得到的。(注意,这里的每一个结果都是一个流)。
4.抽取子流和链接流
调用stream.limit(n)会返回一个新的流,它在n个元素以后结束(若是原来的流更短,那么就会在流结束时结束)。这个方法对于裁剪无限流的尺寸会显得特别有用。例如:
Stream<Double> randoms=Stream.generate(Math::random).limit(100);
会产生一个包含100个随机数的流。
调用stream.skip(n)正好相反:它会丢弃前n个元素。这个方法在将文本分隔为单词时会显得很方便,由于按照split方法的工做方式,第一个元素是没什么用的空字符串,咱们能够经过调用skip来跳过它:
Stream<String> words=Stream.of(contents.split("\\PL+")).skip(1);
咱们能够用Stream类的静态的concat方法将两个流链接起来:
Stream<String> combined=Stream.concat( letters("Hello"),letters("World")); //Yields the stream ["H","e","l","l","o","W","o","r","l","d"]
固然,第一个流不该该是无限的,不然第二个流永远都不会获得处理的机会。
java.util.stream.Stream
Stream<T> limit(long maxSize)
产生一个流,其中包含了当前流中最初的maxSize个元素。
Stream<T> skip(long n)
产生一个流,它的元素是当前流中除了前n个元素以外的全部元素。
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
产生一个流,它的元素是a的元素后面跟着b的元素。
5.其余的流转换
distinct方法会返回一个流,它的元素是从原有流中产生,即原来的元素按照一样的顺序剔除重复元素后产生的。这个流显然可以记住它已经看到过的元素。
Stream<String> uniqueWords =Stream.of("merrily","merrily","merrily","gently").distinct(); //Only one "merrily is retained
对于流的排序,有多种sorted方法的变体可用。其中一种用于操做Comparable元素的流,而另外一种能够接受一个Comparator。下面,咱们对字符串排序,使得最长的字符串排在最前面:
Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::length).reversed());
与全部流转换同样,sorted方法会产生一个新的流,它的元素是原有流中按照顺序排列的元素。
固然,咱们在对集合排序时能够不使用流。可是,当排序处理是流管道的一部分时,sorted方法就会显得颇有用。
最后,peek方法会产生另外一个流,它的元素与原来流中的元素相同,可是在每次获取一个元素时,都会调用一个函数。这对于调试来讲很方便:
Object[] powers=Stream.iterate(1.0,p ->p*2) .peek(e -> System.out.println("Fetching " +e )) .limit(20).toArray();
当实际访问一个元素时,就会打印出来一条信息。经过这种方式,你能够验证iterate返回的无限流是被惰性处理的。
对于调试,你可让peek调用一个你设置了断点的方法。
java.util.stream.Stream
Stream<T> distinct()
产生一个流,包含当前流中全部不一样的元素
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
产生一个流,它的元素是当前流中的全部元素按照顺序排列的第一个方法要求元素是实现了Comparable的类的实例。
Stream<T> peek(Consumer<? super T> action)
产生一个流,它与当前流中的元素相同,在获取其中每一个元素时,会将其传递给action。
6.简单约简
如今你已经看到了如何建立和转换流,咱们终于能够讨论最重要的内容了,即从流数据中得到答案。咱们在本节所讨论的方法被称为约简。约简是一种终结操做(terminal operation),它们会将流约简为能够在程序中使用的非流值。
你已经看到过一种简单的约简,count方法会返回流中元素的数量。
其余的简单约简还有max和min,它们会返回最大值和最小值。这里稍做解释,这些方法返回的是一个类型Optional<T>的值,它要么在其中包装了答案,要么表示没有任何值(由于流碰巧为空)。在过去,碰到这种状况返回null是很常见的,可是这样作会致使在未作完备测试的程序中产生空指针异常。Optional类型是一种更好的表示缺乏返回值的方式。
下面展现了能够如何得到流中的最大值:
Optional<String> largest=words.max(String::compareToIgnoreCase); System.out.println("largest: " + largest.orElse(""));
findFirst返回的是非空集合中的第一个值。它一般会在与filter组合使用时显得颇有用。例如,下面展现了如何找到第一个以字母Q开头的单词,前提是存在这样的单词:
Optional<String> startWithQ =words.filter(s -> s.startWith("Q")).findFirst();
若是不强调使用第一个匹配,而是使用任意的匹配均可以,那么就可使用findAny方法。这个方法在并行处理流时会颇有效,由于流能够报告任何它找到的匹配而不是被限制为必须报告的第一个匹配。
Optional<String> startWithQ=words.parallel().filter(s -> s.startWith("Q“)).findAny();
若是只想知道是否存在匹配,那么可使用anyMatch。这个方法会接受一个断言引元,所以不须要使用filter。
boolean aWordStartsWithQ = words.parallel().anyMatch( s-> s.startWith("Q”));
还有allMatch和noneMatch方法。它们分别会在全部元素和没有任何元素匹配断言的状况下返回true。这些方法也能够经过并行运行而获益。
java.util.stream.Stream:
Optional<T> max(Comprator<? super T> comparator)
Optional<T> min(Comprator<? 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。这些操做都是终结操做。
7.Optional类型
Optional<T>对象是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象。对于第一种状况,咱们称为这种值是存在的。Optional<T>类型被当作一种更安全的方式,用来替代类型T的引用,这种引用要么引用某个对象,要么为null。可是,它只有在正确使用的状况下才会更安全。
1)如何使用Optional值
有效地使用Optional的关键是要使用这样的方法:它在值不存在的状况下会产生一个可替代物,而只有在值存在的状况下才会使用这个值。
让咱们来看第一条策略。一般,在没有任何匹配时,咱们会但愿使用某种默认值,多是空字符串:
String result=optionalString.orElse(""); // The wrapped string, or "" if none
你还能够调用代码来计算默认值:
String result=optionalString.orElseGet(() -> Locale.getDefault().getDisplayName()); //The function is only called when needed
或者能够在没有任何值时抛出异常:
String result=optionalString.orElseThrow(IllegalStateException::new); //Supply a method that yields an exception object
你刚刚看到了如何在不存在任何值的状况下产生相应的替代物。另外一条使用可选值的策略是只有在其存在的状况下才消费该值。
ifPresent方法会接受一个函数。若是该可选值存在,那么它会被传递给该函数。不然,不会发生任何事情。
optionalValue.ifPresent(v -> Process v);
例如,若是在该值存在的状况下想要将其添加到某个集中,那么就能够调用
optionalValue.ifPresent(v -> results.add(v));
或者直接调用
optionalValue.ifPresent(results::add);
当调用ifPresent时,从该函数不会返回任何值。若是想要处理函数的结果,应该使用map:
Optional<Boolean> added=optionalValue.map(results::add);
如今added具备三种值之一:在optionalValue存在的状况下包装在Optional中的true或false,以及在optionalValue不存在的状况下空Optional。
注:这个map方法与以前描述的Stream接口的map方法相似。你能够直接将可选值想象成尺寸为0或1的流。结果的尺寸也是0或1,而且在后一种状况中,会应用到函数。
java.util.Optional
T orElse(T other)
产生这个Optional的值,或者在该Optional为空时,产生other。
T orElseGet(Supplier<? extends T> other)
产生这个Optional的值,或者在该Optional为空时,产生调用other的结果。
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
产生这个Optional的值,或者在该Optional为空时,抛出调用exceptionSupplier的结果。
void ifPresent(Consumer<? super T> consumer)
若是该Optional不为空,那么就将它的值传递给consumer。
<U> Optional<U> map(Function<? super T,? extends U> mapper)
产生将该Optional的值传递给mapper后的结果,只要这个Optional不为空且结果不为null,不然产生一个空Optional。
2)不适合使用Optional值的方式
若是没有正确地使用Optional值,那么相比较以往的获得“某物或null”的方式,你并无获得任何好处。
get方法会在Optional值存在的状况下得到其中包装的元素,或者在不存在的状况下抛出一个NoSuchElementException对象。所以,
Optional<T> optionalValue=...; optionalValue.get().someMethod();
并不比下面的方式更安全:
T value=...; value.someMethod();
isPresent方法会报告某个Optional<T>对象是否具备一个值。可是
if(optionalValue.isPresent()) optionalValue.get().someMethod();
并不比下面的方式更容易处理:
if( value != null) value.someMethod();
3)建立Optional值
若是想要编写方法来建立Optional对象,那么有多个方法能够用于此目的,包括Optional.of(result)和Optional.empty()。例如:
public static Optional<Double> inverse(Double x){ return x==0 ? Optional.empty() : Optional.of(1/x);
ofNullable方法被用来做为可能出现的null值和可选值之间的桥梁。Opitonal.ofNullable(obj)会在obj不为null的状况下返回Optional.of(obj),不然会返回Optional.empty()。
java.util.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。
4)用flatMap来构建Optional值的函数
假设你有一个能够产生Optional<T>对象的方法f,而且目标类型T具备一个能够产生Optional<U>对象的方法g。若是它们都是普通的方法,那么你能够经过调用s.f().g()来将它们组合起来。可是这种组合无法工做,由于s.f()的类型为Optional<T>,而不是T。所以,须要调用:
Optional<U> result=s.f().flatMap(T::g);
若是s.f()的值存在,那么g就能够应用到它上面。不然,就会返回一个空Optional<U>。
很显然,若是有更多的能够产生Optional值的方法或Lambda表达式,那么就能够重复此过程。你能够直接将对flatMap的调用连接起来,从而构建由这些步骤构成的管道,只有全部步骤都成功时,该管道才会成功。
例如,考虑前一节安全的inverse方法。假设咱们还有一个安全的平方根:
public static Optional<Double> squareRoot(Double x){ return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
那么你能够像下面这样计算倒数的平方根了:
Optional<Double> result=inverse(x).flatMap(MyMath::squareRoot);
或者,你能够选择下面的方式:
Optional<Double> result=Optional.of(-4,0).flatMap(MyMath::inverse).flatMap(MyMath::squareRoot);
不管是inverse方法仍是squareRoot方法返回Optional.empty(),整个结果都会为空。
注:你已经在Stream接口中看到过flatMap方法,当时这个方法被用来将能够产生流的两个方法组合起来,其实现方式是摊平由流构成的流。若是将可选值当作尺寸为0和1的流来解释,那么Optional.flatMap方法与其操做方式同样。
java.util.Optional
<U> Optional<U> flatMap(Function<? suer T, Optional<U>> mapper)
产生将mapper应用于当前的Optional值所产生的结果,或者在当前Optional为空时,返回一个空Optional。
8.收集结果
当处理完流以后,一般会想要看其元素。此时能够调用iterator方法,它会产生能够用来访问元素的旧式风格的迭代器。
或者,能够调用forEach方法,将某个函数应用于每一个元素:
stream.forEach(System.out::println);
在并行流上,forEach方法会以任意顺序遍历各个元素。若是想要按照流中的顺序来处理它们,能够调用forEachOrdered方法。
能够调用toArray得到由流的元素构成的数组。
由于没法在运行时建立泛型数组,因此表达式stream.toArray()会返回一个Object[]数组。若是想要让数组具备正确的类型,能够将其传递到数组构造器中:
String[] result=stream.toArray(String[]::new); //stream.toArray() has type Object[]
针对将流中的元素收集到另外一目标中,有一个便捷方法collect可用,它会接受一个Collector接口的实例。
Collectors类提供了大量用于生成公共收集器的工厂方法。为了将流收集到列表或集中,能够直接调用:
List<String> result=stream.collect(Collectors.toList());
或
Set<String> result=stream.collect(Collectors.toSet());
若是想要控制得到的集的种类,那么可使用下面的调用:
TreeSet<String> result=stream.collect(Collectors.toCollection(TreeSet::new));
假设想要经过链接来收集流中的全部字符串。咱们能够调用:
String result=stream.collect(Collectors.joining());
若是想要在元素之间增长分隔符,能够将分隔符传递给joining方法:
String result=stream.collect(Collectors.joining(","));
若是流中包含除字符串之外的其余对象,那么咱们须要现将其转换为字符串,就像下面这样:
String result=stream.map(Object::toString).collect(Collectors.joining(","));
若是想要将流的结果约简为总和,平均值,最大值或最小值,可使用summarizing(Int | Long | Double)方法中的某一个。这些方法会接受一个将流对象映射为数据的函数,同时,这些方法会产生类型为(Int | Long | Double)SummaryStatistics的结果,同时计算总和,数量,平均值,最小值和最大值。
IntSummaryStatistics summary=stream.collect( Collectors.summarizingInt(String::length)); double averageWordLength=summary.getAverage(); double maxWordLength=summary.getMax();
9.收集到映射表中
假设咱们有一个Stream<Person>,而且想要将其元素收集到一个映射表中,这样后续就能够经过它们的ID来查找人员了。Collectors.toMap方法有两个函数引元,它们用来产生映射表的键和值。例如,
Map<Integer,String> idToName=people.collect( Collectors.toMap(Person::getID,Person::getName));
在一般状况下,值应该是实际的元素,所以第二个函数可使用Function.indentity()。
Map<Integer,Person> idToName=people.collect( Collectors.toMap(Person::getId,Function.identity()));
若是有多个元素具备相同的键,那么就会存在冲突,收集器将会抛出一个IllegalStateException对象。能够经过提供第3个函数引元来覆盖这种行为,该函数会针对给定的已有值和新值来解决冲突并肯定对应的值。这个函数应该返回已有值,新值或它们的组合。
Stream<Locale> locales=Stream.of(Locale.getAvailableLocales()); Map<String,String> languageNames=locales.collect( Collectors.toMap( Locale::getDisplayLanguage, l -> l.getDisplayLanguage(l), (existingValue,newValue) -> existingValue));
如今,假设咱们想要了解给定国家的全部语言,这样咱们就须要一个Map<String,Set<String>>。例如,“Switzerland”的值是集[French,German,Italian]。首先,咱们为每种语言都存储一个单例集。不管什么时候,只要找到了给定国家的新语言,咱们都会将已有集和新集作并操做。
Map<String,Set<String>> countryLanguageSets=locales.collect( Collectors.toMap( Locale::getDisplayCountry, l -> Collectors.singleton(l.getDisplayLanguage()), (a,b) -> { //Union of a and b Set<String> union=new HashSet<>(a); union.addAll(b); return union; }));
若是想要获得TreeMap,那么能够将构造器做为第4个引元来提供。你必须提供一种合并函数。如今它会产生一个TreeMap:
Map<Integer,Person> idToName=people.collect( Collectors.toMap( Person::getId, Function.identity(), (existingValue,newValue) -> { throw new IllegalStateException();} TreeMap::new));
注:对于每个toMap方法,都有一个等价的能够产生并发映射表的toConcurrentMap方法。单个并发映射表能够用于并行集合处理。当使用并行流时,共享的映射表比合并映射表要更高效。注意,元素再也不是按照流中的顺序收集的,可是一般这不会有什么问题。
10.群组和分区
在上一节中,你看到了如何收集给定国家的全部语言,可是其处理显得有些冗长。你必须为每一个映射表的值都生成单例集,而后指定如何将现有集与新集合并。将具备相同特性的值群聚成组是很是常见的,而且groupingBy方法直接就支持它。
让咱们来看看经过国家来群组Locale的问题。首先,构建该映射表:
Map<String,List<Locale>> countryToLocales =locales.collect( Collectors.groupingBy(Locale::getCountry));
函数Locale::getCountry是群组的分类函数,你如今能够查找给定国家代码对应的全部地点了,例如:
List<Locale> swissLocales=countryToLocales.get("CH"); //Yieds locales [it_CH,de_CH,fr_CH]
当分类函数是断言函数(即返回boolean值的函数)时,流的元素能够分区为两个列表:该函数返回true的元素和其余的元素。在这种状况下,使用partitioningBy比使用groupingBy要更高效。例如,在下面的代码中,咱们将全部Locale分红了使用英语和使用全部其余语言的两类:
Map<Boolean,List<Locale>> englishAndOtherLocales =locales.collect( Collectors.partitioningBy(l -> l.getLanguage().equals("en"))); List<Locale> englishLocales=englishAndOtherLocales.get(true);
注:若是调用groupingByConcurrent方法,就会使用并行流时得到一个被并行组装的并行映射表。这与toConcurrentMap方法彻底相似。
11.下游收集器
groupingBy方法会产生一个映射表,它的每一个值都是一个列表。若是想要以某种方式来处理这些列表,就须要提供一个“下游收集器”。
例如,若是想要得到集而不是列表,那么可使用上一节中看到的Collector.toSet收集器:
Map<String,Set<Locale>> countryToLocaleSet=locales.collect( groupingBy(Locale::getCountry,toSet()));
12.约简操做
java.util.Stream:
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity,BinaryOperator<T> accumulator)
<U> U reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner)
用给定的accumulator函数产生流中元素的累积总和。若是提供了幺元,那么第一个被累计的元素就是该幺元。若是提供了组合器,那么它能够用来将分别累积的各个部分整合成总和。
13.基本类型流
使用包装器是很低效的。流库中具备专门的类IntStream,LongStream和DoubleStream,用来直接存储基本类型值,而无需使用包装器。
14.并行流
流使得并行处理块操做变得很容易。这个过程几乎是自动的,可是须要一些规则。
首先,必须有一个并行流。可使用Collection.parallelStream()方法从任何集合中获取一个并行流:
Stream<String> parallelWords=words.parallelStream();
并且,parallel方法能够将任意的顺序流转换为并行流。
Stream<String> parallelWords=Stream.of(wordArray).parallel();
只要在终结方法执行时,流处于并行模式,那么全部的中间流操做都将被并行化。
当流操做并行运行时,其目标是要让其返回结果与顺序执行时返回的结果相同。重要的是,这些操做能够以任意顺序执行。
假设你想要对字符串流中的全部短单词计数:
int[] shortWords=new int[12]; words.parallelStream().forEach( s ->{ if(s.length<12) shortWords[s.length()]++;}); //Error-race condition! System.out.println(Arrays.toString(shortWords));
这是一种很是很是糟糕的代码。传递给forEach的函数会在多个并发线程中运行,每一个都会更新共享的数组,这是一种经典的竞争状况。
你的职责是要确保传递给并行流操做的任何函数均可以安全地并行执行,达到这个目的的最佳方式是远离易变状态。在本例中,若是用长度将字符串群组,而后分别对它们进行计数,那么就能够安全地并行化这项运算。
Map<Integer,Long> shortWordCounts= words.parallelStream() .filter(s ->s.length() <10) .collect(groupingBy( String::length, counting()));
注:传递给并行流操做的函数不该该被堵塞。并行流使用fork-join池来操做流的各个部分。若是多个流操做被阻塞,那么池可能就没法作任何事情了。
默认状况下,从有序集合(数组和列表),范围,生成器和迭代产生的流,或者经过调用Stream.sorted产生的流,都是有序的。它们的结果是按照原来元素的顺序积累的,所以是彻底可预知的。若是运行相同的操做两次,将会获得彻底相同的结果。
排序并不排斥高效的并行处理。例如,当计算stream.map(fun)时,流能够被划分为n的部分,它们会被并行地处理。而后,结果将会按照顺序从新组装起来。
当放弃排序需求时,有些操做能够被更有效地并行化。