Stream系列:html
繼Java 8系列之Lambda表達式以後,咱們來了解Stream。Stream 是用函數式編程方式在集合類上進行復雜操做的工具,其集成了Java 8中的衆多新特性之一的聚合操做,開發者能夠更容易地使用Lambda表達式,而且更方便地實現對集合的查找、遍歷、過濾以及常見計算等。java
爲了學習聚合的使用,在這裏,先定義一個數據類:編程
public class Student { int no; String name; String sex; float height; public Student(int no, String name, String sex, float height) { this.no = no; this.name = name; this.sex = sex; this.height = height; } **** } Student stuA = new Student(1, "A", "M", 184); Student stuB = new Student(2, "B", "G", 163); Student stuC = new Student(3, "C", "M", 175); Student stuD = new Student(4, "D", "G", 158); Student stuE = new Student(5, "E", "M", 170); List<Student> list = new ArrayList<>(); list.add(stuA); list.add(stuB); list.add(stuC); list.add(stuD); list.add(stuE);
現有一個List list裏面有5個Studeng對象,假如咱們想獲取Sex=「G」的Student,並打印出來。若是按照咱們原來的處理模式,必然會想到一個for循環就搞定了,而在for循環實際上是一個封裝了迭代的語法塊。在這裏,咱們採用Iterator進行迭代:api
Iterator<Student> iterator = list.iterator(); while(iterator.hasNext()) { Student stu = iterator.next(); if (stu.getSex().equals("G")) { System.out.println(stu.toString()); } }
整個迭代過程是這樣的:首先調用iterator方法,產生一個新的Iterator對象,進而控制整
個迭代過程,這就是外部迭代 迭代過程經過顯式調用Iterator對象的hasNext和next方法完成迭代數組
而在Java 8中,咱們能夠採用聚合操做:dom
list.stream() .filter(student -> student.getSex().equals("G")) .forEach(student -> System.out.println(student.toString()));
首先,經過stream方法建立Stream,而後再經過filter方法對源數據進行過濾,最後經過foeEach方法進行迭代。在聚合操做中,與Labda表達式一塊兒使用,顯得代碼更加的簡潔。這裏值得注意的是,咱們首先是stream方法的調用,其與iterator做用同樣的做用同樣,該方法不是返回一個控制迭代的 Iterator 對象,而是返回內部迭代中的相應接口: Stream,其一系列的操做都是在操做Stream,直到feach時纔會操做結果,這種迭代方式稱爲內部迭代。ide
外部迭代和內部迭代(聚合操做)都是對集合的迭代,可是在機制上仍是有必定的差別:函數式編程
聚合操做是Java 8針對集合類,使編程更爲便利的方式,能夠與Lambda表達式一塊兒使用,達到更加簡潔的目的。函數
前面例子中,對聚合操做的使用能夠歸結爲3個部分:工具
在一次聚合操做中,能夠有多個Intermediate,可是有且只有一個Terminal。也就是說,在對一個Stream能夠進行屢次轉換操做,並非每次都對Stream的每一個元素執行轉換。並不像for循環中,循環N次,其時間複雜度就是N。轉換操做是lazy(惰性求值)的,只有在Terminal操做執行時,纔會一次性執行。能夠這麼認爲,Stream 裏有個操做函數的集合,每次轉換操做就是把轉換函數放入這個集合中,在 Terminal 操做的時候循環 Stream 對應的集合,而後對每一個元素執行全部的函數。
剛纔提到的Stream的操做有Intermediate、Terminal和Short-circuiting:
Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered
Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator
Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
像filter這樣只描述Stream,最終不產生新集合的方法叫做惰性求值方法;而像count這樣最終會從Stream產生值的方法叫做及早求值方法。
long count = allArtists.stream() .filter(artist -> { System.out.println(artist.getName()); return artist.isFrom("London"); }) .count();
如何判斷一個操做是惰性求值仍是及早求值,其實很簡單,只須要看其返回值便可:若是返回值是Stream,那麼就是惰性求值;若是返回值不是Stream或者是void,那麼就是及早求值。上面的示例中,只是包含兩步:一個惰性求值-filter和一個及早求值-count。
前面,已經說過,在一個Stream操做中,能夠有屢次惰性求值,但有且僅有一次及早求值。
咱們有多種方式生成Stream:
Stream接口的靜態工廠方法(注意:Java8裏接口能夠帶靜態方法);
Collection接口和數組的默認方法(默認方法,也使Java的新特性之一,後續介紹),把一個Collection對象轉換成Stream
其餘
of方法,其生成的Stream是有限長度的,Stream的長度爲其內的元素個數。
- of(T... values):返回含有多個T元素的Stream - of(T t):返回含有一個T元素的Stream
示例:
Stream<Integer> integerStream = Stream.of(1, 2, 3); Stream<String> stringStream = Stream.of("A");
generator方法,返回一個無限長度的Stream,其元素由Supplier接口的提供。在Supplier是一個函數接口,只封裝了一個get()方法,其用來返回任何泛型的值,該結果在不一樣的時間內,返回的可能相同也可能不相同,沒有特殊的要求。
- generate(Supplier<T> s):返回一個無限長度的Stream
- 這種情形一般用於隨機數、常量的 Stream,或者須要先後元素間維持着某種狀態信息的 Stream。
- 把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。
示例:
Stream<Double> generateA = Stream.generate(new Supplier<Double>() { @Override public Double get() { return java.lang.Math.random(); } }); Stream<Double> generateB = Stream.generate(()-> java.lang.Math.random()); Stream<Double> generateC = Stream.generate(java.lang.Math::random);
以上三種形式達到的效果是同樣的,只不過是下面的兩個採用了Lambda表達式,簡化了代碼,其實際效果就是返回一個隨機值。通常無限長度的Stream會與filter、limit等配合使用,不然Stream會無限制的執行下去,後果可想而知,若是你有興趣,不妨試一下。
iterate方法,其返回的也是一個無限長度的Stream,與generate方法不一樣的是,其是經過函數f迭代對給指定的元素種子而產生無限連續有序Stream,其中包含的元素能夠認爲是:seed,f(seed),f(f(seed))無限循環。
- iterate(T seed, UnaryOperator<T> f)
示例:
Stream.iterate(1, item -> item + 1) .limit(10) .forEach(System.out::println); // 打印結果:1,2,3,4,5,6,7,8,9,10
上面示例,種子爲1,也可認爲該Stream的第一個元素,經過f函數來產生第二個元素。接着,第二個元素,做爲產生第三個元素的種子,從而產生了第三個元素,以此類推下去。須要主要的是,該Stream也是無限長度的,應該使用filter、limit等來截取Stream,不然會一直循環下去。
empty方法返回一個空的順序Stream,該Stream裏面不包含元素項。
在Collection接口中,定義了一個默認方法stream(),用來生成一個Stream。
public interface Collection<E> extends Iterable<E> { *** default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } *** }
在Arrays類,封裝了一些列的Stream方法,不只針對於任何類型的元素採用了泛型,更對於基本類型做了相應的封裝,以便提高Stream的處理效率。
public class Arrays { *** public static <T> Stream<T> stream(T[] array) { return stream(array, 0, array.length); } public static LongStream stream(long[] array) { return stream(array, 0, array.length); } *** }
示例:
int ids[] = new int[]{1, 2, 3, 4}; Arrays.stream(ids) .forEach(System.out::println);
Intermediate主要是用來對Stream作出相應轉換及限制流,其實是將源Stream轉換爲一個新的Stream,以達到需求效果。
concat方法將兩個Stream鏈接在一塊兒,合成一個Stream。若兩個輸入的Stream都時排序的,則新Stream也是排序的;若輸入的Stream中任何一個是並行的,則新的Stream也是並行的;若關閉新的Stream時,原兩個輸入的Stream都將執行關閉處理。
示例:
Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5)) .forEach(integer -> System.out.print(integer + " ")); // 打印結果 // 1 2 3 4 5
distinct方法以達到去除掉原Stream中重複的元素,生成的新Stream中沒有沒有重複的元素。
Stream.of(1,2,3,1,2,3) .distinct() .forEach(System.out::println); // 打印結果:1,2,3
建立了一個Stream(命名爲A),其含有重複的1,2,3等六個元素,而實際上打印結果只有「1,2,3」等3個元素。由於A通過distinct去掉了重複的元素,生成了新的Stream(命名爲B),而B
中只有「1,2,3」這三個元素,因此也就呈現了剛纔所說的打印結果。
filter方法對原Stream按照指定條件過濾,在新建的Stream中,只包含知足條件的元素,將不知足條件的元素過濾掉。
示例:
Stream.of(1, 2, 3, 4, 5) .filter(item -> item > 3) .forEach(System.out::println);// 打印結果:4,5
建立了一個含有1,2,3,4,5等5個整型元素的Stream,filter中設定的過濾條件爲元素值大於3,不然將其過濾。而實際的結果爲4,5。
filter傳入的Lambda表達式必須是Predicate實例,參數能夠爲任意類型,而其返回值必須是boolean類型。
map方法將對於Stream中包含的元素使用給定的轉換函數進行轉換操做,新生成的Stream只包含轉換生成的元素。爲了提升處理效率,官方已封裝好了,三種變形:mapToDouble,mapToInt,mapToLong。其實很好理解,若是想將原Stream中的數據類型,轉換爲double,int或者是long是能夠調用相對應的方法。
示例:
Stream.of("a", "b", "hello") .map(item-> item.toUpperCase()) .forEach(System.out::println); // 打印結果 // A, B, HELLO
傳給map中Lambda表達式,接受了String類型的參數,返回值也是String類型,在轉換行數中,將字母所有改成大寫
map傳入的Lambda表達式必須是Function實例,參數能夠爲任意類型,而其返回值也是任性類型,javac會根據實際情景自行推斷。
flatMap方法與map方法相似,都是將原Stream中的每個元素經過轉換函數轉換,不一樣的是,該換轉函數的對象是一個Stream,也不會再建立一個新的Stream,而是將原Stream的元素取代爲轉換的Stream。若是轉換函數生產的Stream爲null,應由空Stream取代。flatMap有三個對於原始類型的變種方法,分別是:flatMapToInt,flatMapToLong和flatMapToDouble。
示例:
Stream.of(1, 2, 3) .flatMap(integer -> Stream.of(integer * 10)) .forEach(System.out::println); // 打印結果 // 10,20,30
傳給flatMap中的表達式接受了一個Integer類型的參數,經過轉換函數,將原元素乘以10後,生成一個只有該元素的流,該流取代原流中的元素。
flatMap傳入的Lambda表達式必須是Function實例,參數能夠爲任意類型,而其返回值類型必須是一個Stream。
peek方法生成一個包含原Stream的全部元素的新Stream,同時會提供一個消費函數(Consumer實例),新Stream每一個元素被消費的時候都會執行給定的消費函數,而且消費函數優先執行
示例:
Stream.of(1, 2, 3, 4, 5) .peek(integer -> System.out.println("accept:" + integer)) .forEach(System.out::println); // 打印結果 // accept:1 // 1 // accept:2 // 2 // accept:3 // 3 // accept:4 // 4 // accept:5 // 5
skip方法將過濾掉原Stream中的前N個元素,返回剩下的元素所組成的新Stream。若是原Stream的元素個數大於N,將返回原Stream的後(原Stream長度-N)個元素所組成的新Stream;若是原Stream的元素個數小於或等於N,將返回一個空Stream。
示例:
Stream.of(1, 2, 3,4,5)
.skip(2)
.forEach(System.out::println);
// 打印結果
// 3,4,5
sorted方法將對原Stream進行排序,返回一個有序列的新Stream。sorterd有兩種變體sorted(),sorted(Comparator),前者將默認使用Object.equals(Object)進行排序,然後者接受一個自定義排序規則函數(Comparator),可按照意願排序。
示例:
Stream.of(5, 4, 3, 2, 1) .sorted() .forEach(System.out::println); // 打印結果 // 1,2,3,4,5 Stream.of(1, 2, 3, 4, 5) .sorted() .forEach(System.out::println); // 打印結果 // 5, 4, 3, 2, 1
Java 8系列之Stream的強大工具Collector
Java 8系列之重構和定製收集器
count方法將返回Stream中元素的個數。
示例:
long count = Stream.of(1, 2, 3, 4, 5) .count(); System.out.println("count:" + count);// 打印結果:count:5
forEach方法前面已經用了好屢次,其用於遍歷Stream中的所元素,避免了使用for循環,讓代碼更簡潔,邏輯更清晰。
示例:
Stream.of(5, 4, 3, 2, 1) .sorted() .forEach(System.out::println); // 打印結果 // 1,2,3,4,5
forEachOrdered方法與forEach相似,都是遍歷Stream中的全部元素,不一樣的是,若是該Stream預先設定了順序,會按照預先設定的順序執行(Stream是無序的),默認爲元素插入的順序。
示例:
Stream.of(5,2,1,4,3) .forEachOrdered(integer -> { System.out.println("integer:"+integer); }); // 打印結果 // integer:5 // integer:2 // integer:1 // integer:4 // integer:3
max方法根據指定的Comparator,返回一個Optional,該Optional中的value值就是Stream中最大的元素。至於Optional是啥,後續再作介紹吧。
原Stream根據比較器Comparator,進行排序(升序或者是降序),所謂的最大值就是重新進行排序的,max就是取從新排序後的最後一個值,而min取排序後的第一個值。
示例:
Optional<Integer> max = Stream.of(1, 2, 3, 4, 5) .max((o1, o2) -> o2 - o1); System.out.println("max:" + max.get());// 打印結果:max:1
對於原Stream指定了Comparator,其實是找出該Stream中的最小值,不過,在max方法中找最小值,更能體現出來Comparator的做用吧。max的值不言而喻,就是1了。
min方法根據指定的Comparator,返回一個Optional,該Optional中的value值就是Stream中最小的元素。至於Optional是啥,後續再作介紹吧。
示例:
Optional<Integer> max = Stream.of(1, 2, 3, 4, 5) .max((o1, o2) -> o1 - o2); System.out.println("max:" + max.get());// 打印結果:min:5
剛纔在max方法中,咱們找的是Stream中的最小值,在min中咱們找的是Stream中的最大值,不論是最大值仍是最小值起決定做用的是Comparator,它決定了元素比較大小的原則。
allMatch操做用於判斷Stream中的元素是否所有知足指定條件。若是所有知足條件返回true,不然返回false。
示例:
boolean allMatch = Stream.of(1, 2, 3, 4) .allMatch(integer -> integer > 0); System.out.println("allMatch: " + allMatch); // 打印結果:allMatch: true
anyMatch操做用於判斷Stream中的是否有知足指定條件的元素。若是最少有一個知足條件返回true,不然返回false。
示例:
boolean anyMatch = Stream.of(1, 2, 3, 4) .anyMatch(integer -> integer > 3); System.out.println("anyMatch: " + anyMatch); // 打印結果:anyMatch: true
findAny操做用於獲取含有Stream中的某個元素的Optional,若是Stream爲空,則返回一個空的Optional。因爲此操做的行動是不肯定的,其會自由的選擇Stream中的任何元素。在並行操做中,在同一個Stram中屢次調用,可能會不一樣的結果。在串行調用時,Debug了幾回,發現每次都是獲取的第一個元素,我的感受在串行調用時,應該默認的是獲取第一個元素。
示例:
Optional<Integer> any = Stream.of(1, 2, 3, 4).findAny();
findFirst操做用於獲取含有Stream中的第一個元素的Optional,若是Stream爲空,則返回一個空的Optional。若Stream並未排序,可能返回含有Stream中任意元素的Optional。
示例:
Optional<Integer> any = Stream.of(1, 2, 3, 4).findFirst();
limit方法將截取原Stream,截取後Stream的最大長度不能超過指定值N。若是原Stream的元素個數大於N,將截取原Stream的前N個元素;若是原Stream的元素個數小於或等於N,將截取原Stream中的全部元素。
示例:
Stream.of(1, 2, 3,4,5) .limit(2) .forEach(System.out::println); // 打印結果 // 1,2
傳入limit的值爲2,也就是說被截取後的Stream的最大長度爲2,又因爲原Stream中有5個元素,因此將截取原Stream中的前2個元素,生成一個新的Stream。
noneMatch方法將判斷Stream中的全部元素是否知足指定的條件,若是全部元素都不知足條件,返回true;不然,返回false.
示例:
boolean noneMatch = Stream.of(1, 2, 3, 4, 5) .noneMatch(integer -> integer > 10); System.out.println("noneMatch:" + noneMatch); // 打印結果 noneMatch:true boolean noneMatch_ = Stream.of(1, 2, 3, 4, 5) .noneMatch(integer -> integer < 3); System.out.println("noneMatch_:" + noneMatch_); // 打印結果 noneMatch_:false