JDK8實踐--Java 8系列之Stream的基本語法詳解

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

外部迭代和內部迭代(聚合操做)都是對集合的迭代,可是在機制上仍是有必定的差別:函數式編程

  1. 迭代器提供next()、hasNext()等方法,開發者能夠自行控制對元素的處理,以及處理方式,可是隻能順序處理;
  2. stream()方法返回的數據集無next()等方法,開發者沒法控制對元素的迭代,迭代方式是系統內部實現的,同時系統內的迭代也不必定是順序的,還能夠並行,如parallelStream()方法。並行的方式在一些狀況下,能夠大幅提高處理的效率。

Stream

如何使用Stream?

聚合操做是Java 8針對集合類,使編程更爲便利的方式,能夠與Lambda表達式一塊兒使用,達到更加簡潔的目的。函數

前面例子中,對聚合操做的使用能夠歸結爲3個部分:工具

  1. 建立Stream:經過stream()方法,取得集合對象的數據集。
  2. Intermediate:經過一系列中間(Intermediate)方法,對數據集進行過濾、檢索等數據集的再次處理。如上例中,使用filter()方法來對數據集進行過濾。
  3. Terminal經過最終(terminal)方法完成對數據集中元素的處理。如上例中,使用forEach()完成對過濾後元素的打印。

在一次聚合操做中,能夠有多個Intermediate,可是有且只有一個Terminal。也就是說,在對一個Stream能夠進行屢次轉換操做,並非每次都對Stream的每一個元素執行轉換。並不像for循環中,循環N次,其時間複雜度就是N。轉換操做是lazy(惰性求值)的,只有在Terminal操做執行時,纔會一次性執行。能夠這麼認爲,Stream 裏有個操做函數的集合,每次轉換操做就是把轉換函數放入這個集合中,在 Terminal 操做的時候循環 Stream 對應的集合,而後對每一個元素執行全部的函數。

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:

  1. Stream接口的靜態工廠方法(注意:Java8裏接口能夠帶靜態方法);

  2. Collection接口和數組的默認方法(默認方法,也使Java的新特性之一,後續介紹),把一個Collection對象轉換成Stream

  3. 其餘

    • Random.ints()
    • BitSet.stream()
    • Pattern.splitAsStream(java.lang.CharSequence)
    • JarFile.stream()

靜態工廠方法

of

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

generator方法,返回一個無限長度的Stream,其元素由Supplier接口的提供。在Supplier是一個函數接口,只封裝了一個get()方法,其用來返回任何泛型的值,該結果在不一樣的時間內,返回的可能相同也可能不相同,沒有特殊的要求。

- generate(Supplier<T> s):返回一個無限長度的Stream
  1. 這種情形一般用於隨機數、常量的 Stream,或者須要先後元素間維持着某種狀態信息的 Stream。
  2. 把 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

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

empty方法返回一個空的順序Stream,該Stream裏面不包含元素項。

Collection接口和數組的默認方法

在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);

其餘

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

Intermediate

Intermediate主要是用來對Stream作出相應轉換及限制流,其實是將源Stream轉換爲一個新的Stream,以達到需求效果。

concat

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

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

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

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

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

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

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

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

Terminal

collect

Java 8系列之Stream的強大工具Collector 
Java 8系列之重構和定製收集器

count

count方法將返回Stream中元素的個數。

示例:

long count = Stream.of(1, 2, 3, 4, 5)
        .count();
System.out.println("count:" + count);// 打印結果:count:5

forEach

forEach方法前面已經用了好屢次,其用於遍歷Stream中的所元素,避免了使用for循環,讓代碼更簡潔,邏輯更清晰。

示例:

Stream.of(5, 4, 3, 2, 1)
    .sorted()
    .forEach(System.out::println);
    // 打印結果
    // 1,2,3,4,5

forEachOrdered

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

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

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,它決定了元素比較大小的原則。

reduce

Java 8系列之Stream中萬能的reduce

Short-circuiting

allMatch

allMatch操做用於判斷Stream中的元素是否所有知足指定條件。若是所有知足條件返回true,不然返回false。

示例:

boolean allMatch = Stream.of(1, 2, 3, 4)
    .allMatch(integer -> integer > 0);
System.out.println("allMatch: " + allMatch); // 打印結果:allMatch: true

anyMatch

anyMatch操做用於判斷Stream中的是否有知足指定條件的元素。若是最少有一個知足條件返回true,不然返回false。

示例:

boolean anyMatch = Stream.of(1, 2, 3, 4)
    .anyMatch(integer -> integer > 3);
System.out.println("anyMatch: " + anyMatch); // 打印結果:anyMatch: true

findAny

findAny操做用於獲取含有Stream中的某個元素的Optional,若是Stream爲空,則返回一個空的Optional。因爲此操做的行動是不肯定的,其會自由的選擇Stream中的任何元素。在並行操做中,在同一個Stram中屢次調用,可能會不一樣的結果。在串行調用時,Debug了幾回,發現每次都是獲取的第一個元素,我的感受在串行調用時,應該默認的是獲取第一個元素。

示例:

Optional<Integer> any = Stream.of(1, 2, 3, 4).findAny();

findFirst

findFirst操做用於獲取含有Stream中的第一個元素的Optional,若是Stream爲空,則返回一個空的Optional。若Stream並未排序,可能返回含有Stream中任意元素的Optional。

示例:

Optional<Integer> any = Stream.of(1, 2, 3, 4).findFirst();

limit

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

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

參考資料

  1. Java 8 中的 Streams API 詳解
  2. Java8初體驗(二)Stream語法詳解
  3. Java 8 聚合操做詳解
  4. java8 reduce方法中的第三個參數combiner有什麼做用?
相關文章
相關標籤/搜索