CoreJava2 Reading Note(1:Stream)

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的部分,它們會被並行地處理。而後,結果將會按照順序從新組裝起來。

  當放棄排序需求時,有些操做能夠被更有效地並行化。

相關文章
相關標籤/搜索