Java中的Stream流
一、 初識Stream流
Stream流是Java8後發佈的一系列新特性中的一種,Stream流和我們以前學習過的IO流並不一樣,Stream流的特性支持程序的開發人員以函數式的方式、更爲簡單高效的操作集合、數組等數據結構,大大提高了程序的效率和可閱讀性。
1.1、爲什麼要引入Stream流,它的好處在哪?
我們以前學習過的集合框架中的兩大接口:Collection和Map都支持直接或間接的遍歷操作,我們對集合中的元素進行訪問時,無非就是遍歷、增刪改查幾種操作,而其中最常用到的就是集合的遍歷。但是當我們要對一個集合進行多步操作時,這種循環遍歷的方式就顯得比較臃腫。
看下面這個案例:
/**
* 使用以前學習的集合遍歷對名字進行篩選
*/
import java.util.ArrayList;
/**
* 案例:存在一個五個人姓名的集合,
* 現在要做的操作:
* 1、將集合中名字以德開開始的人添加到一個集合中
* 2、新集合中的名字長度大於4的名字在添加到另一個新集合中
* 3、遍歷最後的新集合,查看其中的元素
*/
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("卡薩丁");
list1.add("卡莎");
list1.add("阿狸");
list1.add("德萊厄斯");
list1.add("德瑪西亞皇子");
//把以德開頭的名字挑出來,存儲到一個新集合
ArrayList<String> list2 = new ArrayList<>();
for (String name : list1) {
if (name.startsWith("德")){
list2.add(name);
}
}
//把名字長度大於4的挑出來,存儲到一個新集合
ArrayList<String> list3 = new ArrayList<>();
for (String name : list2) {
if (name.length() > 4){
list3.add(name);
}
}
//遍歷最後得到的這個集合
for (String name : list3) {
System.out.println(name);
}
}
}
執行結果:
我們再使用Stream流對上面的案例進行實現:
代碼:
import java.util.ArrayList;
import java.util.stream.Stream;
/**
* 使用Stream實現對集合元素的篩選和遍歷
*/
public class StreamDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("卡薩丁");
list.add("卡莎");
list.add("阿狸");
list.add("德萊厄斯");
list.add("德瑪西亞皇子");
/*Java8 Colleation新加的方法:
default Stream<E> stream()
返回一個序列 Stream與集合的來源。*/
list.stream()
//挑選符合條件的元素
.filter(name -> name.startsWith("德"))
.filter(name -> name.length() > 4)
//遍歷得到的結果
.forEach((name)-> System.out.println(name));
}
}
執行結果:
我們可以看到使用Stream流的方式實現上述案例,代碼中並沒有體現出多次循環的遍歷,而是把要做的操作交給了Stream去做。
二、什麼是流式思想 ?
流式思想就類似於工廠的 “生產流水線” 。
當你要對多個元素進行相同的多步操作時,應該先拼接好一個操作的模型步驟,然後再按照這個模型去執行。
就拿Stream來說:
上圖就是Stream中的過濾、映射、跳過、計數多步操作,這就是一種處理集合元素的模型,圖中的每一個矩形都是一個 “流” ,調用指定的方法,可以從一個流模型轉換到另一個模型。
上面的filter、map、skip 都是在對模型進行操作,但執行這些模型的是時候,集合元素並沒有被真正的處理,只有當終結方法count執行的時候,整個模型纔會按照流程去執行,這些延遲操作的原因是來自Lambda表達式的特性。
三、Stream流的特性
3.1 Stream流 其實是一個集合元素的函數模型,它不是集合也不是基本的數據結構,本身不存儲任何的元素。
3.2 Stream流是一個來自數據源的元素隊列
1、元素是特定類型的對象,形成一個隊列;
2、數據源可以是 數組、集合等。
3.3 Stream流的兩個基礎特性
1、Pipelining:中間操作都會返回流對象的本身,這樣的話多個操作就會形成一個類似於生產鏈的模型,就像流式風格,這麼做的好處就是
可以對操作進行優化,比如延遲執行或者短路。
2、forEach:對Collection接口中的集合進行遍歷的話可以通過迭代器或者for循環進行遍歷,這樣顯式的在集合外部進行的遍歷,被叫做外
部迭代,而Stream流內部提供了內部迭代的方法,流可以直接調用遍歷的方法。
3.4 使用Stream流的一般步驟:
1、獲取一個數據源;
2、對數據進行轉換;
3、執行操作獲取結果。
每次執行數據轉換原有的 Stream 對象不變,返回一個新的 Stream 對象,這就允許操作才能像生產車間一樣的函數模型。
四、獲取流的方式
方式1:通過 Collection 接口的默認方法獲取:
default Stream<E> stream()
返回一個序列 Stream與集合的來源。
方式2:通過 Stream 接口的靜態方法 of 獲取
static <T> Stream<T> of(T... values) 參數爲可變參,可以傳數組
返回一個元素爲指定值的順序排列的流。
static <T> Stream<T> of(T t)
返回一個包含一個元素的順序 Stream。
代碼演示:
import java.util.*;
import java.util.stream.Stream;
/**
* 獲取Stream流的方式
*/
public class StreamDemo1 {
public static void main(String[] args) {
//把集合轉換爲流
//1、通過Collection接口中的集合獲取
ArrayList<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
HashSet<Integer> set = new HashSet<>();
Stream<Integer> stream2 = set.stream();
//2、通過Map獲取到鍵和值,在獲取Stream
HashMap<Integer, String> hashMap = new HashMap<>();
Set<Integer> keySet = hashMap.keySet();
Stream<Integer> stream3 = keySet.stream();
Collection<String> values = hashMap.values();
Stream<String> stream4 = values.stream();
Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
Stream<Map.Entry<Integer, String>> stream5 = entries.stream();
//把數組轉換爲Stream
Stream<Integer> stream6 = Stream.of(1, 2, 3);
String[] strArr = {"a","ab","abc"};
Stream<String> stream7 = Stream.of(strArr);
}
}
Stream流中的常用方法:
Stream 流中的方法可以被分爲兩種:延遲方法和終結方法
● 延遲方法 :方法的返回值類型還是 Stream 類型,所以才支持鏈式調用。
● 終結方法 :方法的返回值類型不是 Stream 類型,所以不再支持鏈式調用。
方法 forEach: 用來遍歷流中的數據,是終結方法。
void forEach(Consumer<? super T> action):方法的參數是一個Consumer接口,可以使用Lambda表達式。
代碼演示:
import java.util.stream.Stream;
public class StreamDemo2 {
public static void main(String[] args) {
String[] strArr = {"Harden", "Curry", "Kobe", "Durant"};
Stream<String> stream = Stream.of(strArr);
stream.forEach((String name)->{
System.out.println(name);
});
}
}
執行結果:
方法 filter: 將一個流轉換爲另一個流
Stream<T> filter(Predicate<? super T> predicate) :參數是函數式接口,可使用Lambda表達式
返回由該流的元素組成的流,該元素與給定的謂詞匹配。
代碼演示:
import java.util.stream.Stream;
public class StreamDemo3 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Lebron", "Daniels", "Davis", "Pope", "Caruso");
Stream<String> stream1 = stream.filter((String name) -> {
return name.startsWith("D");
});
stream1.forEach(name -> System.out.println(name));
}
}
在這裏我們要注意的:Stream屬於管道流,只能使用一次,像上面的代碼中,如果第一個Stream流調用方法完畢,流中的數據就會轉移到下一個流中,這時間第一個Stream流就已經關閉了,不能再被使用了。
執行結果:
方法 map: 將一個流中的數據映射到另一個流中
<R> Stream<R> map(Funcation<? super T,? extends R> mapper):
該接口需要一個Function函數式接口參數,可以將當前流中的 T 類型數據轉換爲另一種 R 類型的流。
代碼演示:
import java.util.stream.Stream;
public class StreamDemo4 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3", "4", "5");
Stream<Integer> stream1 = stream.map((String s) -> {
return Integer.parseInt(s);
});
stream1.forEach((integer -> System.out.println(integer)));
}
}
執行結果:
終結方法 count:統計個數
long count(): 用來統計流中的數據個數
代碼演示:
import java.util.stream.Stream;
public class StreamDemo5 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 5, 4, 6, 9, 8, 7);
Stream<Integer> stream1 = stream.filter((Integer integer) -> {
//篩選出比5大的元素
return integer > 5;
});
long count = stream1.count();
System.out.println(count);
}
}
執行結果:
方法 limit:用於截取流中的數據
Stream<T> limit(long maxSize)
返回一個包含該流的元素流,截斷長度不超過 maxSize。
代碼演示:
import java.util.ArrayList;
import java.util.stream.Stream;
public class StreamDemo6 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
list.add("zhaoliu");
Stream<String> stream = list.stream();
Stream<String> stream1 = stream.limit(2);
stream1.forEach(name-> System.out.println(name));
}
}
執行結果:
方法 skip:跳過流中的前幾個元素
Stream<T> skip(long n)
返回一個包含此流的其餘部分丟棄的流的第一 n元素後流。如果這個流包含少於 n元素然後將返回空流。
代碼演示:
import java.util.stream.Stream;
public class StreamDemo7 {
public static void main(String[] args) {
String[] strings = {"a", "b", "c", "d", "e"};
Stream<String> stream = Stream.of(strings);
Stream<String> stream1 = stream.skip(3);
stream1.forEach(s-> System.out.println(s));
}
}
執行結果:
方法 concat:把流連接到一起
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
創建一個懶洋洋的級聯流的元素的所有元素的第一流通過第二個流的元素。將得到的流排序,
如果這兩個輸入流的命令,和並行,如果任一的輸入流是平行的。當所產生的流被關閉時,兩個輸入流的關閉處理程序被調用。
代碼演示:
import java.util.stream.Stream;
public class StreamDemo8 {
public static void main(String[] args) {
String[] strings1 = {"張三", "李四", "王五", "旺財", "大黃"};
String[] strings2 = {"A", "B", "C"};
Stream<String> stream1 = Stream.of(strings1);
Stream<String> stream2 = Stream.of(strings2);
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(s -> System.out.println(s));
}
}
執行結果:
Stream流的用法基本就是以上這些,Java中常用的函數式接口見上篇。