詳解Java中Stream流的用法和原理
一、創(chuàng)建不可變的集合
不可變的集合,顧名思義,就是不想讓別人修改集合中的內(nèi)容,也無法修改。如何創(chuàng)建呢?
(一)創(chuàng)建格式
在List、Set、Map接口中,都存在靜態(tài)的of方法,可以獲取一個不可變的集合:
- static List of(E...elements) 創(chuàng)建一個具有指定元素的List集合對象。
- static Set of(E...elements) 創(chuàng)建一個具有指定元素的Set集合對象。
- static <K , V> Map<K,V> of(E...elements) 創(chuàng)建一個具有指定元素的Map集合對象。
(List.of在jdk8中沒有,需要jdk9及以上的版本。)
public static void main(String[] args) {
List<String> list = List.of("張三","李四","王五");
for(String tmp : list){
System.out.println(tmp);
}
System.out.println("----------------------------");
Set<Integer> set = Set.of(1,2,3,4);
for(int x : set){
System.out.println(x);
}
System.out.println("-----------------------------");
Map<String, Integer> map = Map.of("王五", 1, "李四", 2);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for(Map.Entry<String, Integer> entry : entries){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("------------------------------");
}
- 這些集合不能添加,不能刪除,不能修改,如果修改會拋異常:
public static void main(String[] args) {
List<String> list = List.of("張三","李四","王五");
list.remove(0);
}結(jié)果:

- 這里的Set集合中不能有相同的值,Map不能有相同的key,否則拋異常。
public static void main(String[] args) {
Set<Integer> set = Set.of(1,1,2,3,4);
}
Map.of()里的參數(shù)最多傳 10 個鍵值對,源碼里of()方法參數(shù)最多的也就10對。

那為什么不用可變參數(shù)呢?因為可變參數(shù)只能有一個,并且要在參數(shù)列表的末尾。如果想要存超過10個的鍵值對,我們可以利用數(shù)組->可變參數(shù):
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c");
map.put(4,"d");
map.put(5,"e");
map.put(6,"f");
map.put(7,"g");
map.put(8,"h");
map.put(9,"i");
map.put(10,"j");
map.put(11,"k");
map.put(12,"l");
//獲取所有的鍵值對
Set<Map.Entry<Integer,String>> entries = map.entrySet();
//把entries變成一個數(shù)組
//如果集合的長度 〉數(shù)組的長度﹔數(shù)據(jù)在數(shù)組中放不下,此時會根據(jù)實際數(shù)據(jù)的個數(shù),重新創(chuàng)建數(shù)組
//如果集合的長度〈=數(shù)組的長度:數(shù)據(jù)在數(shù)組中放的下,此時不會創(chuàng)建新的數(shù)組,而是直接用原來的數(shù)組
Map.Entry[] array = entries.toArray(new Map.Entry[0]);
//生成不可變map集合,ofEntries()的參數(shù)是可變參數(shù),可變參數(shù)可以使用數(shù)組。
Map map1 = Map.ofEntries(array);
}第二種方式,但是要jdk10及以上:
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c");
map.put(4,"d");
map.put(5,"e");
map.put(6,"f");
map.put(7,"g");
map.put(8,"h");
map.put(9,"i");
map.put(10,"j");
map.put(11,"k");
map.put(12,"l");
//生成不可變集合
Map<Integer,String> map1 = Map.copyOf(map);
}(二)不可變集合的作用
- 保證線程安全:不可變集合是一種在創(chuàng)建之后就不再變更的對象,這種特性使得它們天生支持線程安全。
- 防止數(shù)據(jù)被篡改:不可變集合可以用于封裝一些敏感或重要的數(shù)據(jù),防止它們被外部程序或不可信的庫修改或破壞。
二、Stream 流
(一)Stream 的思想
Stream 為什么叫流?可以通過下面的案例來引入這個概念。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥");
list.add("陳小帥");
list.add("小紅");
list.add("李四");
list.add("陳四");
//篩選姓陳的,并且名字是3個字的人
list.stream().filter(name->name.startsWith("陳"))
.filter(name->name.length()==3)
.forEach(name-> System.out.println(name));
}Stream 就像流水線一樣,這個流水線第一道工序就是過濾掉不姓陳的,第二道工序過濾掉字的數(shù)量不等于3的,最后的工序就是打印。
(二)Stream流的使用步驟
- 得到一條Stream流水線,把數(shù)據(jù)放上去。
- 利用Stream流中的API進行各種操作。
中間方法:方法調(diào)用后還可以調(diào)用其它方法,如上面的filter。
終結(jié)方法:最后一步,調(diào)用后不能調(diào)用其它方法。
1.把數(shù)據(jù)放到流水線上
| 獲取方式 | 方法名 | 說明 |
|---|---|---|
| 單列集合 | default Stream stream() | collection中的默認方法(由于單列集合實現(xiàn)了Collection接口,可以直接.stream()) |
| 雙列集合(Map等) | 無 | 無法直接使用stream流 |
| 數(shù)組 | public static Stream stream(T[] array) | Arrays工具類中的靜態(tài)方法 |
| 零碎數(shù)據(jù) | public static Stream stream(T[] array) | Stream接口中的靜態(tài)方法 |
public static void main(String[] args) {
//1.單列集合
List<String> list = new ArrayList<>();
Collections.addAll(list,"a","b","b","b","c","d","e");//添加數(shù)據(jù)
list.stream().forEach(s -> System.out.println(s));//流水線操作
//2.雙列集合
Map<Integer,String> map = new HashMap<>();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c");
map.put(4,"d");
map.put(5,"e");
map.put(6,"f");
map.keySet().stream().forEach(s-> System.out.println(s));//方式1:獲取key的流水線,利用key與value的關系來操作整體
map.entrySet().stream().forEach(entry-> System.out.println(entry));//方式2:獲取鍵值對的流水線
//3.數(shù)組
int[] arr = {1,2,3,4,5,6,7,8};
Arrays.stream(arr).forEach(x-> System.out.println(x));//利用 Arrays 工具類來獲取流
String[] strs = {"a","b","c"};
Arrays.stream(strs).forEach(str-> System.out.println(str));
//4.零散的數(shù)據(jù)
Stream.of("a","b","c","d","e").forEach(x-> System.out.println(x));//利用 Stream 來獲取
}2.中間方法
| Stream方法 | 中文解釋 | 作用 |
|---|---|---|
| Stream filter(Predicate<? super T> predicate) | 篩選 | 過濾出符合條件的元素 |
| Stream limit(long maxSize) | 限制 | 截取前maxSize個元素 |
| Stream skip(long n) | 跳過 | 跳過前n個元素 |
| Stream distinct() | 去重 | 去除重復的元素(根據(jù)hashCode和equals方法) |
| static Stream concat(Stream a, Stream b) | 合并 | 將兩個Stream合并為一個 |
| Stream map(Function<T, R> mapper) | 映射 | 將每個元素轉(zhuǎn)換為另一種類型或形式 |
注意1:每個中間方法都是返回新的Stream流,原來的Stream流只能使用一次,建議使用鏈式編程。
注意2:修改Stream流中的數(shù)據(jù),不會影響原來集合或者數(shù)組中的數(shù)據(jù)。
- filter
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥");
list.add("陳小帥");
list.add("小紅");
list.add("李四");
list.add("陳四");
//篩選姓陳的,并且名字是3個字的人
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//如果返回值為true,表示當前數(shù)據(jù)留下
//如果返回值為false,表示當前數(shù)據(jù)舍棄不要
return s.startsWith("陳");
}
}).forEach(s -> System.out.println(s));//通過匿名內(nèi)部類
list.stream().filter(s->s.startsWith("陳")).forEach(s -> System.out.println(s));//通過 lambda
}
- limit
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥");
list.add("陳小帥");
list.add("小紅");
list.add("李四");
list.add("陳四");
//截取前 3 個數(shù)據(jù)
list.stream().limit(3).forEach(s -> System.out.println(s));
}
- skip
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥");
list.add("陳小帥");
list.add("小紅");
list.add("李四");
list.add("陳四");
//跳過前 3 個數(shù)據(jù)
list.stream().skip(3).forEach(s -> System.out.println(s));
}
- distinct
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥");
list.add("陳大帥");
list.add("陳大帥");
list.add("陳大帥");
list.add("陳小帥");
list.add("陳小帥");
list.add("陳小帥");
list.add("陳小帥");
list.add("小紅");
list.add("李四");
list.add("陳四");
//去重
list.stream().distinct().forEach(s -> System.out.println(s));
}
- concat
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥");
list.add("陳小帥");
list.add("小紅");
list.add("李四");
list.add("陳四");
List<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("b");
list2.add("c");
//合并,如果類型不相同,類型就會變?yōu)楣餐母割?
Stream.concat(list.stream(),list2.stream()).forEach(s -> System.out.println(s));
}
- 將每個元素轉(zhuǎn)換為另一種類型或形式
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥-20");
list.add("陳小帥-19");
list.add("小紅-11");
list.add("李四-12");
list.add("陳四-13");
list.stream().map(new Function<String, Integer>() {
//第一個類型:流中原本的數(shù)據(jù)類型
// 第二個類型:要轉(zhuǎn)成之后的類型
@Override
public Integer apply(String s) {
//apply的形參s:依次表示流里面的每一個數(shù)據(jù)
//返回值:表示轉(zhuǎn)換之后的數(shù)據(jù)
String[] split = s.split("-");
int age = Integer.parseInt(split[1]);
return age;
}
}).forEach(age-> System.out.println(age));//內(nèi)部類
list.stream().map(s->Integer.parseInt(s.split("-")[1])).forEach(age-> System.out.println(age));//lambda
}
3.終結(jié)方法
| Stream方法 | 中文解釋 | 作用 |
|---|---|---|
| void forEach(Consumer action) | 遍歷 | 對每個元素執(zhí)行指定的操作 |
| long count() | 統(tǒng)計 | 返回元素的個數(shù) |
| toArray() | 收集到數(shù)組 | 返回一個包含所有元素的數(shù)組 |
| collect(Collector collector) | 收集到集合 | 返回一個包含所有元素的集合,可以指定集合的類型和特性 |
- forEach
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥-20");
list.add("陳小帥-19");
list.add("小紅-11");
list.add("李四-12");
list.add("陳四-13");
// consumer的泛型:表示流中數(shù)據(jù)的類型
list.stream().forEach(new Consumer<String>() {
//accept方法的形參s:依次表示流里面的每一個數(shù)據(jù)
@Override
public void accept(String s) {
System.out.println(s);
}
});
list.stream().forEach(s -> System.out.println(s));//lambda
}- count
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥-20");
list.add("陳小帥-19");
list.add("小紅-11");
list.add("李四-12");
list.add("陳四-13");
//統(tǒng)計集合中的個數(shù)
long count = list.stream().count();
System.out.println(count);
}
- toArray
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥-20");
list.add("陳小帥-19");
list.add("小紅-11");
list.add("李四-12");
list.add("陳四-13");
//方式一:
//收集集合中的數(shù)據(jù)到數(shù)組
Object[] array = list.stream().toArray();
System.out.println(Arrays.toString(array));
//方式二:指定對于類型
//IntFunction的泛型:具體類型的數(shù)組
//toArray方法的參數(shù):負責創(chuàng)建一個指定類型的數(shù)組
//toArray方法的底層:會依次得到流里面的每一個數(shù)據(jù),并把數(shù)據(jù)放到數(shù)組當中
String[] strings = list.stream().toArray(new IntFunction<String[]>() {
/**
*
* @param value 流中數(shù)據(jù)的個數(shù),要跟數(shù)組長度保持一致
* @return 具體類型的數(shù)組
*/
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(strings));
//方式三:使用 lambda 表達式
String[] array1 = list.stream().toArray(value -> new String[value]);//lambda
System.out.println(Arrays.toString(array1));
}
- collect
收集到List、Set中:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陳大帥");
list.add("陳大帥");
list.add("陳大帥");
list.add("陳小帥");
list.add("陳小帥");
list.add("陳小帥");
list.add("小紅");
list.add("李四");
list.add("陳四");
//收集到list中
List<String> newList = list.stream().filter(s->s.startsWith("陳")).collect(Collectors.toList());
System.out.println(newList);
//收集到Set中,自動去重
Set<String> set = list.stream().filter(s -> s.startsWith("陳")).collect(Collectors.toSet());
System.out.println(set);
}收集到Map中:
public static void main(String[] args) {
List<String> list2 = new ArrayList<>();
list2.add("陳大帥-20");
list2.add("陳小帥-12");
list2.add("小紅-15");
list2.add("李四-15");
list2.add("陳四-38");
//收集到Map中,鍵:姓名 值:年齡,注意:收集到Map中的時候,流中數(shù)據(jù) key 不能重復
Map<String,Integer> map = list2.stream()
.filter(s->s.startsWith("陳"))
/**
* toMap:參數(shù)一表示鍵的生成規(guī)則
* 參數(shù)二表示值的生成規(guī)則
* 參數(shù)一:
* Function 泛型一:表示流中數(shù)據(jù)類型
* 泛型二:表示Map集合中key的數(shù)據(jù)類型
* 方法 apply 形參:依次表示流里面的每一個數(shù)據(jù)
* 返回值: Map 的 key
* 參數(shù)二:
* Function 泛型一:表示流中數(shù)據(jù)類型
* 泛型二:表示Map集合中value的數(shù)據(jù)類型
* 方法 apply 形參:依次表示流里面的每一個數(shù)據(jù)
* 返回值:Map 的 value
*/
.collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
return s.split("-")[0];
}
}, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s.split("-")[1]);
}
}));
System.out.println(map);
//使用 lambda 表達式
Map<String,Integer> map2 =
list2.stream()
.filter(s->s.startsWith("陳"))
.collect(Collectors.toMap(s->s.split("-")[0],s->Integer.parseInt(s.split("-")[1])));
System.out.println(map2);
}結(jié)果:

如果流中有重復的key,集合到Map時會報錯,這跟集合到List不一樣。
public static void main(String[] args) {
List<String> list2 = new ArrayList<>();
list2.add("陳大帥-20");
list2.add("陳大帥-21");
list2.add("陳小帥-12");
list2.add("小紅-15");
list2.add("李四-15");
list2.add("陳四-38");
//使用 lambda 表達式
Map<String,Integer> map2 =
list2.stream()
.filter(s->s.startsWith("陳"))
.collect(Collectors.toMap(s->s.split("-")[0],s->Integer.parseInt(s.split("-")[1])));
System.out.println(map2);
}結(jié)果:

以上就是詳解Java中Stream流的用法和原理的詳細內(nèi)容,更多關于Java Stream流的資料請關注腳本之家其它相關文章!
相關文章
Java數(shù)據(jù)結(jié)構(gòu)學習之樹
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)學習之樹,文中有非常詳細的代碼示例,對正在學習java數(shù)據(jù)結(jié)構(gòu)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-05-05
springboot項目同時啟動web服務和grpc服務的方法
本文主要介紹了springboot項目同時啟動web服務和grpc服務的方法,通過實際代碼示例展示了實現(xiàn),對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-02-02
Maven在Windows中的配置以及IDE中的項目創(chuàng)建(圖文教程)
這篇文章主要介紹了Maven在Windows中的配置以及IDE中的項目創(chuàng)建(圖文教程),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09
SpringCloud-Alibaba-Sentinel-配置持久化策略詳解
這篇文章主要介紹了SpringCloud-Alibaba-Sentinel-配置持久化策略,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
解決Idea報錯There is not enough memory
在使用Idea開發(fā)過程中,可能會遇到因內(nèi)存不足導致的閃退問題,出現(xiàn)"There is not enough memory to perform the requested operation"錯誤時,可以通過調(diào)整Idea的虛擬機選項來解決,方法是在Idea的Help菜單中選擇Edit Custom VM Options2024-11-11

