JavaEE學習日誌持續更新----> 必看!JavaEE學習路線(文章總彙)
Java學習日誌(十六)
Stream
Stream:就是一個流式思想,可以把集合或數組,轉化爲一個Stream流,使用Stream流中的方法,來操作集合或數組
引言
現在,我們有一個集合,它有以下元素
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三丰");
需求:過濾掉集合中以張開頭的元素,然後過濾掉字符串長度爲3的元素,最後再遍歷輸出每個元素
使用傳統的方式(循環遍歷),對集合中的元素進行過濾,並遍歷集合
public class Demo01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三丰");
//對list集合中的元素進行過濾,只要以張開頭的元素,存儲到一個新的集合
List<String> listZhang = new ArrayList<>();
for (String name : list) {
if(name.startsWith("張")){
listZhang.add(name);
}
}
//對listZhang集合中的元素進行過濾,只要名字長度爲3的元素,存儲到一個新的集合
List<String> listThree = new ArrayList<>();
for (String name : listZhang) {
if(name.length()==3){
listThree.add(name);
}
}
//遍歷listThree
for (String name : listThree) {
System.out.println(name);
}
}
}
這段代碼中含有三個循環,每一個作用不同:
- 首先篩選所有姓張的人;
- 然後篩選名字有三個字的人;
- 後進行對結果進行打印輸出。
每當我們需要對集合中的元素進行操作的時候,總是需要進行循環、循環、再循環。這是理所當然的麼?不是。循環是做事情的方式,而不是目的。另一方面,使用線性循環就意味着只能遍歷一次。如果希望再次遍歷,只能再使用另一個循環從頭開始。
所以就有了Lambda的衍生物Stream
示例:Stream可以使代碼更加優雅
public class Demo02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三丰");
//對list集合中的元素進行過濾,只要以張開頭的元素,存儲到一個新的集合
//對listZhang集合中的元素進行過濾,只要名字長度爲3的元素,存儲到一個新的集合
//遍歷listThree
//把list轉化爲流
/*list.stream()
.filter(name->name.startsWith("張"))
.filter(name->name.length()==3)
.forEach(name->System.out.println(name));*/
//方法引用
list.stream()
.filter(name->name.startsWith("張"))//過濾以張開頭的元素
.filter(name->name.length()==3)//過濾長度爲3的元素
.forEach(System.out::println);//遍歷
}
}
流式模型
Stream流:需要先創建一條流式模型,根據模型操作數據
注意:
- 這裏的 filter 、 map 、 skip 都是在對函數模型進行操作,集合元素並沒有真正被處理。只有當終結方法 count 執行的時候,整個模型纔會按照指定策略執行操作。而這得益於Lambda的延遲執行特性。
- Stream流只操作數據,並不保存數據。當一次操作結束以後,會產生一個新的Stream流,原來的Stream流會關閉。
獲取Stream流的方式
java.util.stream.Stream<泛型> 是Java 8新加入的最常用的流接口
獲取Stream流的方式
- 所有的Collection集合都可以通過stream默認方法獲取流。
default Stream<E> stream()
:返回以此集合爲源的順序 Stream 。
注意:此方法都是單列集合Collection中的方法,Map集合不能直接使用 - Stream接口的靜態方法of可以獲取數組對應的流
static <T> Stream<T> of(T... values)
:傳遞可變參數,把可變參數轉換爲Stream流。
注意:可變參數底層就是一個數組,所以也可以傳遞數組,創建Stream流
示例一:Collection集合獲取Stream流
- list集合獲取Stream流
//創建list集合
List<String> list = new ArrayList<>();
//使用List集合中的方法stream,把集合轉化爲流
Stream<String> stream1 = list.stream();
- set集合獲取Stream流
//創建set集合
Set<String> set = new HashSet<>();
//使用set集合中的方法stream,把集合轉化爲流
Stream<String> stream2 = set.stream();
- Map集合獲取Stream流(間接獲取)
//第一種:轉化爲set或Colleciton集合來獲取流
Map<String,String> map = new HashMap<>();
Set<String> keySet = map.keySet();//獲取所有的鍵
Stream<String> stream3 = keySet.stream();
Collection<String> values = map.values();//獲取所有的值
Stream<String> stream4 = values.stream();
//第二種:通過entry來獲取流
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
示例二:數組獲取Stream流
- 可變參數獲取Stream流
//Stream接口的靜態方法of創建stream流
Stream<String> stream1 = Stream.of("a", "b", "c", "d");
Stream<Integer> stream2 = Stream.of(1,2,3,4);
- 數組獲取流
//可變參數底層就是一個數組,所以也可以傳遞數組,創建Stream流
String[] arr1 = {"a", "b", "c", "d"};
Stream<String> stream3 = Stream.of(arr1);
Integer[] arr2 = {1,2,3,4};
Stream<Integer> stream4 = Stream.of(arr2);
注意:以下寫法錯誤,數組的數據類型必須使用包裝類,不能使用基本類型
int[] arr3 = {1,2,3,4,5};
Stream<int[]> stream5 = Stream.of(arr3);
常用方法
Stream流的方法分爲兩種方法:
- 終結方法:返回值類型不再是 Stream 接口自身類型的方法,因此不再支持類似 StringBuilder 那樣的鏈式調 用。本小節中,終結方法包括 count 和 forEach 方法。
- 非終結方法:返回值類型仍然是 Stream 接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其餘 方法均爲非終結方法。)
foreach:遍歷
void forEach(Consumer<? super T> action)
對此流的每個元素執行操作。
是一個終結方法,返回值不是Stream,不能再使用Stream中的方法了。
參數:函數式接口Consumer,所以可以使用lambda表達式,重寫accept方法
作用:對Stream流進行遍歷
示例:
public class Demo02 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//方法引用
stream.forEach(System.out::println);
}
}
注意:若使用了兩次Stream流,則會報IllegalStateException: stream has already been operated upon or closed
異常。第二次使用Stream中的方法forEach,拋出了非法的狀態異常,流是一個流式模型,流使用完畢之後,數據就流轉到下一步,這時上一步的流就已經關閉,所以就不能再使用了,Stream流只能使用一次。
public class Demo02 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//第一次使用Stream流
stream.forEach( s-> System.out.println(s));
//第二次使用Stream流,出現異常!
stream.forEach(System.out::println);
}
}
filter:過濾
Stream<T> filter(Predicate<? super T> predicate)
返回由與此給定謂詞匹配的此流的元素組成的流。
參數:函數式接口Predicate,所以可以使用lambda表達式,重寫test方法
作用:對Stream流中的元素進行過濾,形成一個新的Stream流
示例:對Stream流中的元素進行過濾,只要包含"羊羊",並對新Stream流進行遍歷
public class Demo03 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//對Stream流中的元素進行過濾,只要包含"羊羊"
Stream<String> streamYY = stream.filter(s -> s.contains("羊羊"));
//遍歷Stream
streamYY.forEach(System.out::println);
}
}
count:統計
long count()
返回此流中元素的數量。
是一個終結方法,返回值不是Stream,不能再使用Stream中的方法了
作用:統計Stream流中元素的個數
示例:
public class Demo04 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
long count = stream.count();
System.out.println(count);//8
}
}
limit:獲取
Stream<T> limit(long maxSize)
返回由此流的元素組成的流,截斷長度不超過 maxSize。
作用:獲取前幾個元素,把元素存儲到一個新的Stream流中
例如:
- limit(3)獲取流中前3個元素
- limit(n)獲取流中前n個元素
示例:獲取Stream流中前5個元素,並遍歷
public class Demo05 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//獲取Stream流中的前5個元素
Stream<String> limitStream = stream.limit(5);
//遍歷
limitStream.forEach(System.out::println);
}
}
skip:跳過
Stream<T> skip(long n)
在丟棄流的第一個 n元素後,返回由此流的其餘元素組成的流。
作用:跳過前幾個元素,把剩餘的元素存儲到一個新的Stream流中
例如:skip(3):跳過前三個元素
示例:跳過Stream流的前5個元素,並遍歷
public class Demo06 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//跳過前五個元素
Stream<String> stringStream = stream.skip(5);
//遍歷
stringStream.forEach(System.out::println);
}
}
map:映射
<R> Stream<R> map(Function<? super T,? extends R> mapper)
返回一個流,該流包含將給定函數應用於此流的元素的結果。
參數:函數式接口Function,所以可以使用lambda表達式,重寫apply方法
作用:映射,把String流的數據類型轉化爲另外一種數據類型的Stream流
示例:使用map方法把String類型的Stream映射爲Integer類型的Stream
public class Demo07 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stringStream = Stream.of("1", "2", "3", "4", "5");
//使用map方法把String類型的Stream映射爲Integer類型的Stream
Stream<Integer> integerStream = stringStream.map((String s) -> {
return Integer.parseInt(s);
});
//遍歷
integerStream.forEach(System.out::println);
}
}
練習:使用map方法把String類型的姓名映射爲Person類型的對象
public class Demo08 {
public static void main(String[] args) {
//創建一個存儲String的Stream流
Stream<String> stream = Stream.of("張三", "李四", "王五", "趙六", "田七");
//Stream<Person> personStream = stream.map(name-> new Person(name));
Stream<Person> personStream = stream.map(Person::new);
//遍歷
personStream.forEach(System.out::println);
}
}
concat(靜態方法):組合
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
創建一個延遲連接的流,其元素是第一個流的所有元素,後跟第二個流的所有元素。
作用:把兩個Stream流合成一個
示例:使用Stream中的靜態方法concat,把兩個流合成一個新的流
public class Demo09 {
public static void main(String[] args) {
//創建一個Stream1流
Stream<String> stream1 = Stream.of("張三", "李四", "王五", "趙六", "田七");
//創建一個Stream2流
Stream<String> stream2 = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//使用Stream中的靜態方法concat,把兩個流合成一個新的流
Stream<String> stream = Stream.concat(stream1, stream2);
//遍歷
stream.forEach(System.out::println);
}
}
收集Stream結果
Stream流轉化爲集合
- 轉化爲List集合:
stream.collect(Collectors.toList());
public class Demo01 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//轉化爲List集合:stream.collect(Collectors.toList());
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
}
}
- 轉化爲Set集合:
stream.collect(Collectors.toSet());
public class Demo01 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set);
}
}
Stream流轉化爲數組
轉化爲數組:stream.toArray()
返回值爲Object類型
public class Demo02 {
public static void main(String[] args) {
//創建一個Stream流
Stream<String> stream = Stream.of("美羊羊", "喜羊羊", "慢羊羊", "懶羊羊", "沸羊羊", "灰太狼", "紅太狼", "小灰灰");
//使用Stream流中的方法toArray
Object[] arr = stream.toArray();
for (Object o : arr) {
System.out.println(o);
}
}
}
Stream流綜合案例
需求:
現在有兩個 ArrayList 集合存儲隊伍當中的多個成員姓名,要求使用Stream流進行以下若干操作步驟:
- 第一個隊伍只要名字爲3個字的成員姓名;
- 第一個隊伍篩選之後只要前3個人;
- 第二個隊伍只要姓張的成員姓名;
- 第二個隊伍篩選之後不要前2個人;
- 將兩個隊伍合併爲一個隊伍;
- 根據姓名創建 Person 對象;
- 打印整個隊伍的Person對象信息。
public class Demo01 {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪麗熱巴");
one.add("宋遠橋");
one.add("蘇星河");
one.add("老子");
one.add("莊子");
one.add("孫子");
one.add("洪七公");
//1. 第一個隊伍只要名字爲3個字的成員姓名;
//2. 第一個隊伍篩選之後只要前3個人;
//把集合轉化成流
Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("張無忌");
two.add("張三丰");
two.add("趙麗穎");
two.add("張二狗");
two.add("張天愛");
two.add("張三");
//3. 第二個隊伍只要姓張的成員姓名;
//4. 第二個隊伍篩選之後不要前2個人;
//把集合轉化爲Stream流
Stream<String> twoStream = two.stream().filter(name -> name.startsWith("張")).skip(2);
//5. 將兩個隊伍合併爲一個隊伍;
//6. 根據姓名創建 Person 對象;
//7. 打印整個隊伍的Person對象信息。
Stream.concat(oneStream,twoStream).map(Person::new).forEach(System.out::println);
}
}