Java之Stream流
Stream流的初步學習
初次學習Stream流的學習筆記,學習之前先了解一下函數式接口
概述
API是一個程序向使用者提供的一些方法,通過這些方法就能實現某些功能.所以對於流API來
說,重點是怎麽理解"流"這個概念,所謂的流:就是數據的渠道,所以,流代表的是一個對象的
序列.它和Java I/O類裏使用的"流"不同.雖然在概念上與java.util.stream中定義的流是類
似的,但它們是不同的.流API中的流是描述某個流類型的對象.
流API中的流操作的數據源,是數組或者是集合.它本身是不存儲數據的,只是移動數據,在移動
過程中可能會對數據進行過濾,排序或者其它操作.但是,一般情況下(絕大數情況下),流操作
本身不會修改數據源.比如,對流排序不會修改數據源的順序.相反,它會創建一個新的流,其
中包含排序後的結果.
“Stream流”其實是一個集合元素的函數模型,它並不是集合,也不是數據結構,其本身並不存儲任何
元素(或其地址值)。
Stream(流)是一個來自數據源的元素隊列
- 元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。
- 數據源 流的來源。 可以是集合,數組 等。
和以前的Collection操作不同, Stream操作還有兩個基礎的特征:
- Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent
style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。 - 內部叠代: 以前對集合遍歷都是通過Iterator或者增強for的方式, 顯式的在集合外部進行叠代, 這叫做外部叠
代。 Stream提供了內部叠代的方式,流可以直接調用遍歷方法。
當使用一個流的時候,通常包括三個基本步驟:獲取一個數據源(source)→ 數據轉換→執行操作獲取想要的結
果,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以
像鏈條一樣排列,變成一個管道。
獲取流
java.util.stream.Stream
獲取一個流非常簡單,有以下幾種常用的方式:
- 所有的Collection 集合都可以通過stream 默認方法獲取流;
- default Stream
- Stream 接口的靜態方法of 可以獲取數組對應的流。
static
可以看出,Stream流是與容器相關的.
下面例子演示如何獲取Stream流:
package com.wzlove.stream.demo2;
import java.util.*;
import java.util.stream.Stream;
/**
* @創建人 王智
* @創建時間 2018/7/28
* @描述 演示獲取Stream流
*/
public class ConvertStream {
public static void main(String[] args) {
// 單列集合根據Collection的stream方法獲取流
// 數組集合
ArrayList<String> arrayList = new ArrayList<>();
Stream<String> stream1 = arrayList.stream();
// set集合
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
// Map集合不能直接轉化為Stream流,但是可以先將Map集合轉化為Set集合
Map<String,String> map = new HashMap<>();
// map集合轉化為Set集合再轉化為Stream流
Stream<String> stream3 = map.keySet().stream();
Stream<String> stream4 = map.values().stream();
Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();
// 使用第二種方式獲取Stream流
// 傳遞數組
Stream<int[]> stream6 = Stream.of(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
// 傳入可變參數
Stream<Integer> stream7 = Stream.of(1, 2, 3, 4, 5, 6);
}
}
Stream的常用方法
流模型的操作很豐富,這裏介紹一些常用的API。這些方法可以被分成兩種:
- 延遲方法:返回值類型仍然是Stream 接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其余方
法均為延遲方法。) - 終結方法:返回值類型不再是Stream 接口自身類型的方法,因此不再支持類似StringBuilder 那樣的鏈式調
用。本小節中,終結方法包括count 和forEach 方法。
註意 : Stream流只能被使用一次,再次使用會拋出異常,說流已經關閉.
逐一處理
方法不一一演示,最後統一演示:
- void forEach(Consumer<? super T> action);該方法接收一個Consumer 接口函數,會將每一個流元素交給該函數進行處理。
終結方法,遍歷之後就不能再調用Stream流中的其他方法
過濾
- Stream
映射
統計個數
- long count();該方法返回一個long值代表元素個數(不再像舊集合那樣是int值)
也是一個終結方法
取用前幾個
- Stream
跳過前幾個
- Stream
組合
- static
備註:這是一個靜態方法,與java.lang.String 當中的concat 方法是不同的。
演示前幾個常用方法:
package com.wzlove.stream.demo2;
import java.util.ArrayList;
/**
* @創建人 王智
* @創建時間 2018/7/28
* @描述 測試Stream流的常用方法
*/
public class StreamUsuallyMethod {
public static void main(String[] args) {
// 準備數據
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(5);
arrayList.add(3);
arrayList.add(9);
arrayList.add(1);
arrayList.add(4);
arrayList.add(2);
arrayList.add(3);
arrayList.add(6);
arrayList.add(8);
arrayList.add(7);
// 測試Stream的forEach方法
System.out.print("forEach方法的測試 : ");
arrayList.stream().forEach(elem -> System.out.print(elem + " "));
System.out.println();
// 測試Filter方法
// 獲取集合中大於5的數字
System.out.print("獲取集合中大於5的數字 : ");
arrayList.stream().filter(elem -> elem > 5).forEach(elem -> System.out.print(elem + " "));
System.out.println();
// 獲取集合中大於2小於7的數字
System.out.print("獲取集合中大於2小於7的數字 : ");
arrayList.stream().filter(elem -> elem > 2).filter(elem -> elem < 7).forEach(elem -> System.out.print(elem + " "));
System.out.println();
// 測試Map方法
// 將集合轉化為String集合
System.out.print("map方法將int轉化為String : ");
arrayList.stream().map(elem -> String.valueOf(elem)).forEach(elem -> System.out.print(elem + " "));
System.out.println();
}
}
舉個例子,使用所有的常用方法:
package com.wzlove.stream.demo2;
import java.util.ArrayList;
import java.util.stream.Stream;
/**
* @author 王智
* @date 2018/7/28
* @描述
* 1. 第一個隊伍只要名字為3個字的成員姓名;存儲到一個新集合中。
* 2. 第一個隊伍篩選之後只要前3個人;存儲到一個新集合中。
* 3. 第二個隊伍只要姓張的成員姓名;存儲到一個新集合中。
* 4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
* 5. 將兩個隊伍合並為一個隊伍;存儲到一個新集合中。
* 6. 根據姓名創建Person 對象;存儲到一個新集合中。
* 7. 打印整個隊伍的Person對象信息。
*/
public class StreamDemo {
public static void main(String[] args) {
//第一支隊伍
ArrayList<String> one = new ArrayList<>();
one.add("迪麗熱巴");
one.add("宋遠橋");
one.add("蘇星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("莊子");
one.add("洪七公");
//第二支隊伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜紮");
two.add("張無忌");
two.add("趙麗穎");
two.add("張三豐");
two.add("尼古拉斯趙四");
two.add("張天愛");
two.add("張二狗");
/*
* 1. 第一個隊伍只要名字為3個字的成員姓名;存儲到一個新集合中。
* 2. 第一個隊伍篩選之後只要前3個人;存儲到一個新集合中。
* 3. 第二個隊伍只要姓張的成員姓名;存儲到一個新集合中。
* 4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
* 5. 將兩個隊伍合並為一個隊伍;存儲到一個新集合中。
* 6. 根據姓名創建Person 對象;存儲到一個新集合中。
* 7. 打印整個隊伍的Person對象信息。
*/
// 直接使用Stream流來完成
Stream.concat(
one.stream().filter(elem -> elem.length() == 3).limit(3),
two.stream().filter(elem -> elem.startsWith("張")).skip(2))
.map(elem -> new Person(elem))
.forEach(elem -> System.out.println(elem));
}
}
方法引用
很少使用
:: 寫法就是方法的引用,它的出現是為了簡化Lambda的寫法.
System.out 對象中有一個重載的println(String) 方法恰好就是我們所需要的。那麽對於
printString 方法的函數式接口參數,對比下面兩種寫法,完全等效:
- Lambda表達式寫法: s -> System.out.println(s);
- 方法引用寫法: System.out::println
第一種語義是指:拿到參數之後經Lambda之手,繼而傳遞給System.out.println 方法去處理。
第二種等效寫法的語義是指:直接讓System.out 中的println 方法來取代Lambda。兩種寫法的執行效果完全一
樣,而第二種方法引用的寫法復用了已有方案,更加簡潔。
註:Lambda 中 傳遞的參數 一定是方法引用中 的那個方法可以接收的類型,否則會拋出異常
類型有
這些很少用,是要能看懂就行
通過對象名引用成員方法(demo1)
package com.wzlove.method.demo1; /* 定義一個打印的函數式接口 */ @FunctionalInterface public interface Printable { //定義字符串的抽象方法 void print(String s); } package com.wzlove.method.demo1; public class MethodRerObject { //定義一個成員方法,傳遞字符串,把字符串按照大寫輸出 public void printUpperCaseString(String str){ System.out.println(str.toUpperCase()); } } package com.wzlove.method.demo1; /** * @author 王智 * @date 2018/7/28 * @描述 使用方法引用簡化Lambda表達式,對象調用方法 */ public class Demo1 { public static void printString(Printable printable){ printable.print("java"); } public static void main(String[] args) { // 簡化前的寫法 printString(s->{ // 創建對象 MethodRerObject methodRerObject = new MethodRerObject(); // 調用方法 methodRerObject.printUpperCaseString(s); }); // 簡化後的寫法,對象調用方法 // 前提是對象存在,方法存在 printString(new MethodRerObject()::printUpperCaseString); } }
通過類名稱引用靜態方法(demo2)
package com.wzlove.method.demo2;
/**- @author 王智
- @date 2018/7/28
@描述 計算絕對值的接口
*/
@FunctionalInterface
public interface Calcable {int intAbs(int num);
}
package com.wzlove.method.demo2;
/**- @author 王智
- @date 2018/7/28
@描述
*/
public class Demo2 {public static int methodAds(int num, Calcable calcable){
return calcable.intAbs(num);
}public static void main(String[] args) {
// Lambda的寫法
int num1 = methodAds(-100,num->Math.abs(num));
System.out.println(num1);
// 簡化Lambda的寫法
// 前提是類存在,靜態方法存在
int result = methodAds(-100,Math::abs);
System.out.println(result);
}
}
- 通過super引用成員方法
- 通過this引用成員方法
類的構造器的使用(demo3)
package com.wzlove.method.demo3;
public class Person {
private String name;public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; }
}
package com.wzlove.method.demo3;
/
定義一個創建Person對象的函數式接口
/
@FunctionalInterface
public interface PersonBuilder {
//定義一個方法,根據傳遞的姓名,創建Person對象返回
Person builderPerson(String name);
}package com.wzlove.method.demo3;
/**- @author 王智
- @date 2018/7/28
@描述
*/
public class Demo3 {public static void buildPerson(String name, PersonBuilder pb){
Person person = pb.builderPerson(name); System.out.println(person.getName());
}
public static void main(String[] args) {
buildPerson("楊紫",(name)->{ return new Person(name); }); // 方法引用 // 有參構造 buildPerson("迪麗熱巴",Person::new);
}
}
數組的構造器的引用(demo4)
package com.wzlove.method.demo4;
/
定義一個創建數組的函數式接口
/
@FunctionalInterface
public interface ArrayBuilder {
//定義一個創建int類型數組的方法,參數傳遞數組的長度,返回創建好的int類型數組
int[] builderArray(int length);
}package com.wzlove.method.demo4;
/**- @author 王智
- @date 2018/7/28
@描述
*/
public class Demo4 {public static int[] builderArr(int length, ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args) {
int[] arr1 = builderArr(5,length->new int[length]);
// 創建數組
int[] arr = builderArr(5,int[] :: new );
}
}
Java之Stream流