Stream是Java8中處理集合的關鍵抽象概念,它能夠指定你但願對集合進行的操做,可是將執行操做的時間交給具體實現來決定。例如,若是你但願計算某個方法的平均值,你能夠在每一個元素上指定調用的方法,從而得到全部值的 平均值。你可使用Stream API來並行執行操做,使用過多線程來計算每一段的總和與數量,再將結果彙總起來。正則表達式
一個Stream表面上看與一個集合很相似,容許你改變和獲取數據。可是實際上它與集合是有很大區別的:數組
List<String> words = ...; //Java8在Collection接口中新添加的stream方法,能夠將任何集合轉化爲一個Stream long count = words.stream().filter(w -> w.length() > 12).count(); //將stream方法改爲parallel Stream方法,就可讓StreamAPI並行執行過濾和統計操做。 long count = words.vStream().filter(w -> w.length() > 12).count();
Stream遵循「作什麼,而不是怎麼去作」的原則。上面的代碼中,描述了須要作什麼:獲的長單詞並對它們的個數進行統計。咱們沒有制定按照什麼順序,或者在哪一個線程中作,它們都是理所應當發生的。相反,循環在一開始就須要指定如何進行運算,所以就是去了優化的機會。
當你使用Stream時,你會經過三個階段來創建一個操做流水線。安全
在上面的代碼中,經過stream或parallelStream方法來建立Stream,在經過filter方法對其進行轉換,而count方法就是終止操做。多線程
Java8在Collection接口中新添加的stream方法,能夠將任何集合轉化爲一個Stream併發
若是是一個數組,能夠用靜態的Stream.of方法將它轉化爲一個Stream;若是須要將數組的一部分轉化爲Stream,可使用Arrays.stream()dom
Stream<String> words = Stream.of(String[] array); Stream<String> words = Stream.of(String ...args); Stream<String> words = Arrays.stream(array, from, to);
要建立一個不含任何元素的Stream,可使用靜態的Stream.empty()ide
建立無限Stream的靜態方法。函數
generate方法接受一個無參的函數(從技術上說,是一個Supplier <T>接口的對象)。當須要一個Stream值時,就能夠調用該方法來產生一個值。優化
Stream<String> echos = Stream.generate( () -> "Echo");//建立一個含有常量值的Stream Stream<Double> randoms = Stream.generate( Math::random );
iteratre方法接受一個"種子(seed)"值和一個函數(從技術上講,是一個UnaryOperator<T>接口的對象)做爲參數,而且會對以前的值重複應用該函數。例如:ui
Stream<BigInterger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInter.ONE));
在Java8中,添加了許多可以產生Stream的方法。例如,Pattern類添加了一個splitAsStream的方法,可以按照正則表達式對CharSequence(接口,String、StringBuilder和StringBuffer都實現該接口)對象進行分隔。
Stream<String> words = Pattern.compile(",").splitAsStream("abc,def");
Stream轉換是指從一個流中讀取數據,並將轉換後的數據寫入另外一流中。
filter、map、flatMap方法
filter()
filter方法的參數是一個Predicate<T>對象——即一個從T到Boolean的函數。
List<String> wordList = ...; Stream<String> words = wordList.stream(); Stream<String> longWOrds = words.filter(w -> w.length() > 12);
map()
咱們常常須要對一個流中的值進行某種形式的轉換。這時能夠考慮使用map方法,並傳遞給它一個執行轉換的函數。例如
//使用了帶有一個方法引用的map方法 Stream<String> lovercaseWords = words.map(String::toLowerCase); //一般會使用一個lambda表達式來代替方法表達式 Stream<Character> firstChars = words.map(s -> charAt(0)); //該方法產生的流會包含每一個單詞的第一個字符
flatMap()
假設如今有一個函數,以下:
public static Stream<Character> characterStream(String s) { List<Character> result = new ArrayList<>(); for (char c : s.toCharArray()) result.add(c); return result.stream(); }
調用該方法,如characterStream("boat")會返回流['b', 'o', 'a', 't']。假設將該方法映射到一個字符串流上:
Stream<Stream<Character>> result = words.mapo(w -> characterStream(w));
將會獲得一個包含多個流的流,如:[..., ['b', 'o', 'a', 't'], ...]。若是要將其展開爲一個只包含字符串的流[..., 'b', 'o', 'a', 't', ...],則須要使用flatMap方法,而不是map方法:
Stream<Character> letters = words.flatMap(w -> characterStream(w));
提取子流和組合流
Stream<Double> randoms = Stream.generate(Math::random).limit(100);
Stream<String> words = Stream.of("abc", "cdf","ghi").skip(1); //會丟棄掉"abc"
Stream<Character> combined = Stream.concat(Stream.of('H', 'e', 'l', 'l', 'o'), Stream.of('W', 'o', 'r', 'l', 'd')); //產生一個新流['H', 'e', 'l', 'l', 'o','W', 'o', 'r', 'l', 'd']
有狀態的轉換
以前介紹的流轉換都是無狀態的,即當從一個已過濾或已映射的流中獲取某個元素時,結果並不依賴以前的元素。除此以外,Java8也提供了有狀態的轉換。
Stream<String> uniqueWords = Stream.of("merrily", "merrily", "gently").distinct(); // 只獲取一個"merrily"
Stream<String> longestFirst = words.sorted(Comparator.comparing(String::length).reversed());
前面已經瞭解任何創###Stream終止操做建流和轉換流,接下來將介紹: 如何從數據流數據中找到「答案」。
聚合方法
在本節中介紹的方法統稱聚合方法。它們將流聚合爲一個值,以便在程序中使用。聚合方法都是終止操做。當一個流應用了終止操做後,它就不能再應用其它操做了。
count()
返回流中元素總數。
long count = Stream.of("abc", "bcd", "cde").count(); //count的結果爲 3。
max(), min()
max方法返回流中最大值。min方法返回流中最小值。
須要注意的是,它們返回的是一個Optional<T>值,它可能會封裝返回值,也可能表示沒有返回(當流爲空時)。之前在這種狀況一般會返回null,程序拿到該返回值後,進行下一步操做,可能會致使拋出空指針異常。在Java8中,Optional類型時一種更好的表示缺乏返回值的方式。
下面是一個如何得到最大值的示例:
Optional<String> largest = Stream.of("abc", "bcd", "cde").max(String::compareToIgnoreCase); if (largest.isPresent()) System.out.println("largest: " + largest.get());
findFirst(), findAny()
findFirst方法會返回非空集合中的第一個值(一般與filter方法結合使用);若是想找到全部匹配的元素中的任意一個,那麼可使用findAny方法,這個方法在對流進行並行執行時十分有效,由於只要在任何片斷中發現第一個匹配元素,都會結束整個計算。
Optional<String> startsWithQ = Stream.of("abc", Qabc1", "bcd", "Qabc2").parallel().filter(s -> s.startWith("Q")).findAny();
allMatch(), anyMatch()
allMatch方法和anyMatch方法,它們分別在全部元素和沒有元素匹配predicate時返回true。雖然這些方法老是會檢查整個流,可是仍能夠經過並行執行來提升速度。
Optional類型
Optional<T>對象或者是對一個對象的封裝,或者表示不是任何對象。它通常比指向T類型的引用更安全,由於它不會反回null。
若是存在被封裝的對象,那麼get方法會返回該對象,不然會返回一個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();
下面開始瞭解如何真正使用Optional值。
optionalValue.ifPresent(v -> results.add(v));
//或者
optionalValue.ifPresent(results::add);
Optional<Boolean> added = optionalValue.map(results::add);
由於results.add()返回值是boolean類型,如今added有多是如下三種值:被封裝到Optional中的true或者false,或者是一個空的可選值。
String result = optionalString.orElse(""); // 若是封裝的字符串爲空的話,則使用給定的空字符串""
String result = optionalString.orElseGet(() -> System.getProperty("user.dir"));
String result = optionalString.orElseThrow(NoSuchElementException::new); //須要提供一個產生異常對象的方法
聚合操做
略
收集結果
當你處理完流以後,一般只是想看一下結果,而不是將它們聚合爲一個值。
iterator()
該方法會生成一個傳統風格的迭代器,用於訪問元素。
toArray()
因爲沒法在運行時建立一個泛型數組,因此表達式stream.toArray()會返回一個Object[]數組。若是但願獲得一個正確類型的數組,能夠將類型傳遞給數組的構造函數:
String[] result = String.of("abc", "bcd", "cde").toArray(String[]::new);
collect()
下面是如何使用HashSet的collect方法的示例:
HashSet<String> result = String.of("abc", "bcd", "abc").collect(HashSet::new, HashSet::add, HashSet::addAll);
List<String> result = String.of("abc", "bcd").collect(Collectors.toList());
Set<String> result = String.of("abc", "bcd", "abc").collect(Collectors.toSet());
若是但願控制獲得的set類型,可使用以下方式:
TreeSet<String> result = String.of("abc", "bcd", "abc").collect(Collectors.toCollection(TreeSet::new));
Map<Integer, String> idToName = persons.collect(Collectors.toMap(Person::getId, Person::getName));
Map<Integer, Person> idToPerson = persons.collect(Collectors.toMap(Person::getId, Function.identity()));
forEach(), forEachOrdered()
當只要將它們打印出來,或逐個遍歷它們,那麼可使用forEach方法,以下:
Stream.of("abc", "bcd", "cdf").forEach(System.out::println);
向該方法傳遞的函數會被應用到流中的每一個元素上。須要注意的是,在一個並行流上,要確保該函數能夠被併發執行。
在一個並行流上,可能會以任意的順序來訪問元素。若是但願按照流的順序來執行它們,那麼可使用forEachOrdered方法。
forEach方法和forEachOrdered方法都是終止操做。所以在調用它們以後,就不能再使用這個流了。若是但願還能繼續使用這個流,請使用peek方法。
原始類型流
假設有一個整型數組,並將該數組收集到一個Stream<Integer>的流中,不過獎每一個證書包裝成Integer對象顯然是一個低效的作法,對於其餘原始類型double、float、long、short、char、byte及boolean也是同樣。爲此,Stream API提供了IntStream和LongStream和DoubleStream三種類型,專門用來直接存儲原始類型值,沒必要使用包裝,而Stream API設計者認爲不須要爲其它5種原始類型都添加對應的專門類型。
要建立一個IntStream,能夠調用IntStream.of和Arrays.stream方法:
IntStream stream = IntStream.of(1, 3, 5, 7); IntStream stream = Arrays.stream(values, from, to); // values是一個int[]數組
並行流
流使得並行計算變得容易。默認狀況下,流操做會建立一個串行流,方法Collection.parallelStream()除外。parallel方法能夠將任意的串行流轉換爲一個並行流。例如:
Stream<String> parallelWords = Stream.of(wordArray).parallel();
一個並行流,只要在終止方法執行前,流處於並行模式,那麼全部延遲執行的流操做就都會被並行執行。
當並行運行流操做時,須要確保傳遞給並行流操做的函數都是線程安全的,應當返回與串行運行時相同的結果。很重要的一點是,這些操做都是無狀態的,所以能夠以任意順序被執行。
默認狀況下,從有序集合、範圍值、生成器及迭代器,或者調用Stream.storted所產生的流,都是有序的。有序並不會妨礙並行,例如,當計算stream.map(fun)時,流能夠被分爲n段,每一段都會被併發處理。而後再按順序將結果組合起來。
當不考慮有序時,一些操做能夠更有效地並行運行。調用Stream.unordered方法能夠不關心順序。好比,能夠放棄有序來加快limit方法的速度。若是隻須要一個流中的任意n個元素,並不關心具體內容時,能夠調用:
Stream<String> stream = stringStream.parallel().unordered().limit(n);
函數式接口
在前文中,你已經瞭解了許多參數爲函數的操做。例如,Stream.filter方法就將一個函數做爲參數,filter方法的描述以下:
Stream<T> filter( Predicate<? super T> predicate )
filter方法的使用例子,以下:
Stream<String> longWords = words.filter( s -> s.length() >= 12 ); //words是一個含有多個字符串的流
查看文檔能夠知道,Predicate是一個接口,只含有一個返回boolean值的非默認方法:
public interface Predicate { boolean test(T argument); }
在實際開發中,開發人員可能會常常傳一個lambda表達式或者方法引用,因此方法名並不重要。重要的部分是返回boolean值。當查閱文檔時,只須要記住Predicate是一個返回boolean值的函數就好了。
下表總結了可以做爲Stream和Collectors方法參數的函數式接口。
函數式接口 | 參數類型 | 返回類型 | 描述 |
---|---|---|---|
Supplier<T> | 無 | T | 提供一個T類型的值 |
Consumer<T> | T | void | 處理一個T類型的值 |
BiConsumer<T, U> | T, U | void | 處理T類型和U類型的值 |
Predicate<T> | T | boolean | 一個 計算Boolean值的函數 |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> |
T | int long double |
分別計算int、long、double值的函數 |
IntFunction<R> LongFunction<R> DoubleFunction<R> |
int long double |
R | 參數分別爲int、long、double類型的函數 |
Function<T, R> | T | R | 一個參數類型爲T的函數 |
BiFunction<T, U, R> | T, U | R | 一個參數類型爲T和U的函數 |
UnaryOperator<T> | T | T | 對類型T進行的一元操做 |
BinaryOperator<T> | T, T | T | 對類型T進行的二元操做 |