1. 程式人生 > 實用技巧 >Java8-Stream常用的API

Java8-Stream常用的API

Java8-Stream

這是java在1.8中的一個新特性,如果有過大資料相關的經驗或者學習過函數語言程式設計語言的同學可能理解起來更容易一些,它提供的類似於一種SQL語句的高階抽象來進行資料的計算,在大家的開發中使用此中形式會讓大家的程式碼更簡潔更高效。
什麼是Stream?
它是Java8中處理集合的關鍵的抽象,Stream帶有一些處理資料的API,Stream中其實是不儲存資料的,也不是一個數據結構,也並不會修改原有的資料,當它呼叫某個API後生成的資料會儲存在另一個物件中,在Stream的API中,大致上分為兩類,一類是中間操作(Intermediate Operations)和最終操作(Terminal Operations),這一點和Spark中的運算元非常類似,在Spark中是分為轉換運算元和行動運算元,如果Stream在呼叫API時,只有最後一個步驟是最終操作才會執行中間操作鏈,併產生結果,這就好比,大家在小學時完成老師佈置的作業,其實你晚上完成的很好,但是第二天你卻沒交到老師的手裡,那老師肯定不會認為你完成了作業的,所以最後一定要把作業上交到老師的手裡。
宣告:
文章中的Tips和部分想法,全部是博主個人的理解和見解,可能和真實情況有些出入,懇請大佬手下留情!!!
具體的用法:
建立流:
//呼叫java.util.Arrays的Stream方法,可以將一個數組轉化成一個流        
String[] str = {"1","2","3","4","5"};
Stream<String> stream = Arrays.stream(str);
//將一個List轉成Stream,只需要呼叫stream方法即可
List<Integer> list = Lists.newArrayList();
Stream<Integer> stream1 = list.stream();
Stream中靜態方法建立流
//直接Stream中的靜態方法of,可以自定義流的資料,但是資料的型別必須是一致的
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);

// Stream中的靜態方法iterate,需要傳遞兩個引數,
// 第一個是初始值,第二個是應用於上一個元素以產生一個新元素的函式,
// 比如要生成一個從0開始的6個偶數
Stream<Integer> limit = Stream.iterate(0, (x) -> x + 2).limit(6);

// 用於產生一個無序的隨機流,傳遞的引數是一個生成的規則
Stream<Double> limit1 = Stream.generate(Math::random).limit(6);

以上部分程式碼使用了lambda表示式,如果有不會使用的同學可以先往下看,我會在下方說明!!!

部分同學可能發現了,如果轉成Stream後是不能直接列印的,這個時候我們就需要Stream中的forEach進行輸出。
stream.forEach(System.out::println);
接下來介紹一些常用的API的功能及使用
filter:
// filter是根據某一條件進行過濾,過濾的是返回為false的資料,返回true的會被留下來,生成新的Stream
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> integerStream = stream.filter(x -> x > 3);		
limit:
// limit的用法與sql中的一樣,限制返回的個數,返回從0開始的6個偶數				
Stream<Integer> limit = Stream.iterate(0, (x) -> x + 2).limit(6);
skip:
// skip是跳過指定個數的資料
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> skip = stream.skip(2);

Tips:可以使用limit+skip實現分頁,limit限制每頁的數量,skip根據選擇頁數進行跳過對應個數的資料。

distinct:
// distinct的用法與sql中一樣,都是去重的意思
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2);
Stream<Integer> distinct = stream.distinct();
Map&FlatMap:
// map是將stream中的每一個元素進行處理,生成新的元素
String[] str = {"1","2","3","4","5"};
Stream<String> stream = Arrays.stream(str);
Stream<Integer> integerStream = stream.map(Integer::parseInt);

// flatMap將元素,經過處理,返回0個或多個結果
String[] str = {"1,2,3,4,5"};
Stream<String> stream6 = Arrays.stream(str);
Stream<String> stringStream = stream6.flatMap(x -> {
    String[] s = x.split(",");
        return Arrays.stream(s);
});
Tips:在這邊有很多人會疑問,Map和FlatMap到底有什麼區別,其實根據這兩個單詞可以看出,Flat是有平面的意思,在Map上加上Flat,我理解為在Map的基礎上多加一步打平操作,瞭解過Spark的同學應該可以理解,Spark中也有這兩個概念,在使用的過程中,其實FlatMap也可以當作Map來使用。
但是我在工作中發現,這兩個東西還是要根據一定的邏輯進行區分:
Map像工廠給某一個物品架上紙箱的操作,你拿過來的是一個東西,加工後是一個紙箱。
FlatMap像對某一個商品進行拆解,一個商品可能會拆出好多個零件。
但是也有可能這個商品已經是不可拆解的了,所以有可能是一對一的關係,但只要出現一對多的關係就只能用Flatmap。
sorted:
// sorted方法是進行排序,預設是按照自然順序,當然你也可以自定義排序規則,需要實現Comparator介面
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2);
Stream<Integer> sorted = stream.sorted();
如果大家想使用Stream對資料進行一些基本的運算,建議使用Stream自帶的summaryStatistics,它會返回一個IntSummaryStatistics,這個類下面有一些基本的屬性,conut,sum,min,max,average
// summaryStatistics
List<Integer> integerList = Arrays.asList(2,12,23,4,5,68,1,23,4,35,234,123,12,2);
IntSummaryStatistics intSummary = integerList.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println(intSummary.getMax());
System.out.println(intSummary.getCount());
System.out.println(intSummary.getSum());
System.out.println(intSummary.getMin());
System.out.println(intSummary.getAverage());
在上述例子中,大家可以看到有的API並沒有介紹,接下來我們繼續Stream API的介紹,
既然Stream中存在Map操作,與其對應的就是reduce操作,這個在Stream中同樣也存在:
// Stream介面中的原始碼
// reduce實質上是對元素進行規約操作,accumulator是一個函式

// 1.流中的第一個引數為第一個元素,第二個元素來到時將第一個元素處理的結果當作引數
Optional<T> reduce(BinaryOperator<T> accumulator);

// 2.這個與上面不同的地方是把identity作為起始元素
T reduce(T identity, BinaryOperator<T> accumulator);

// 3.在Stream中,該方法與第二個相同,即使有第三個函式也不起作用
// 但是在ParallelStream,多執行緒的時候,每個執行緒執行的是第二個函式的邏輯,
// 在所有執行緒執行的結果後,將結果組合成一個流,呼叫第三個函式的邏輯
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
在流的結尾,大概率會使用Collect,將處理過後流中的資料轉成另外的一個我們可用的資料結構。
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 轉成陣列				
Object[] objects = stream.toArray();
// 轉成list
List<Integer> list = stream.collect(Collectors.toList());
// 轉成set
Set<Integer> set = stream.collect(Collectors.toSet());
// 轉成Map
Stream<Student> student = Stream.of(new Student("a", 10), new Student("b", 11), new Student("c", 12));
Map<String, Integer> map = student.collect(Collectors.toMap(Student::getName, Student::getAge));

// 獲得最大年齡,最小使用minBy
Integer maxAge = student.map(Student::getAge).collect(Collectors.maxBy(Integer::compareTo)).get();
// 求年齡之和
Integer sum = student.collect(Collectors.summingInt(Student::getAge));
// 求年齡的平均值
Double avg = student.collect(Collectors.averagingDouble(Student::getAge));

Tips:其實在Collectors中還有很多很多的方法,大家可以嘗試去使用,這裡就不展開說了。

還可以進行匹配
// allMatch, anyMatch, NoneMatch
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

// allMatch是如果流中元素都滿足,返回true,否則false
boolean b = stream.allMatch(x -> x < 10);

// anyMatch是如果流中有一個元素滿足,返回true,否則false
boolean b1 = stream.anyMatch(x -> x > 10);

// noneMatch是如果流中的元素都不符合,返回true,否則false
boolean b2 = stream.noneMatch(x -> x > 2);

Lambda表示式的簡單使用

其實Lambda表示式有一個統一書寫的格式就是 () -> {}
如果你不需要引數,需要返回值可以這麼寫:
() -> 5
如果需要一個引數,需要返回值可以這樣寫:傳遞進去一個x,返回x+2的值
x -> x + 2
接受兩個引數,並返回值
(x, y) -> x + y
還可以在引數前面加上引數的型別
(int x, int y) -> x + y
有時候對於陣列或者是集合遍歷的方式大多是for迴圈,或者foreach迴圈,現在可以使用lambda進行遍歷,程式碼更簡潔。
// 第一種跟上述的方式相同,第二種是Java8的雙冒號操作符,也可以進行列印
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.forEach((x) -> {
      System.out.print(x + ",");
});
list.forEach(System.out::println);