Stream 基本介紹
1、Java 8中提供了一個(gè)新的附加包,名為Java.util.stream。這個(gè)包由類、接口和枚舉組成,允許對(duì)元素進(jìn)行函數(shù)式操作,您可以使用stream來(lái)過(guò)濾、收集、打印和從一個(gè)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換到另一個(gè);
2、Stream API 借助于同樣新出現(xiàn)的 Lambda 表達(dá)式,極大的提高編程效率和程序可讀性。
Stream使用好處
1、函數(shù)式編程,提高代碼開(kāi)發(fā)效率和簡(jiǎn)潔性;
2、提供多種對(duì)集合數(shù)據(jù)的過(guò)濾、聚合、轉(zhuǎn)換操作,比較方便;
3、將一些sql中復(fù)雜數(shù)據(jù)統(tǒng)計(jì)放到服務(wù)處理,提升性能
Stream使用缺點(diǎn)
1、團(tuán)隊(duì)開(kāi)發(fā)習(xí)慣如果不常用,可能會(huì)影響代碼易讀性;
2、使用stream編寫(xiě)代碼,不容易排錯(cuò)和調(diào)試。
常見(jiàn)使用示例
1、基本數(shù)據(jù)對(duì)象定義
//定義一個(gè)UserInfo類,包含userName、age、sex字段
@Data
public class UserInfo {
private String userName;
private Integer age;
private String sex;
}
/**
* 構(gòu)造測(cè)試數(shù)據(jù)
* @return
*/
public static List<UserInfo> builderData(){
List<UserInfo> userInfoList = new ArrayList<UserInfo>();
userInfoList.add(UserInfo.builder().age(18).idCard(123456).name("小紅").memo("備注").build());
userInfoList.add(UserInfo.builder().age(16).idCard(13456).name("小軍").memo("備注1").build());
userInfoList.add(UserInfo.builder().age(20).idCard(123456).name("小花").memo("備注2").build());
userInfoList.add(UserInfo.builder().age(18).idCard(123456).name("小容").memo("備注3").build());
userInfoList.add(UserInfo.builder().age(18).idCard(123456).name("小容").memo("備注6").build());
userInfoList.add(UserInfo.builder().age(26).idCard(12346).name("小海").memo("備注4").build());
userInfoList.add(UserInfo.builder().age(26).idCard(12346).name("小海").memo("備注5").build());
return userInfoList;
}
2、Stream的創(chuàng)建方法:
① 通過(guò) java.util.Collection.stream() 方法用集合創(chuàng)建流
List<String> list = Arrays.asList("learn","Java8","stream");
// 創(chuàng)建順序流
Stream<String> stream = list.stream();
// 創(chuàng)建并行流
Stream<String> parallelStream = list.parallelStream();
② 使用java.util.Arrays.stream(T[] array)方法用數(shù)組創(chuàng)建流
String[] arrays = {"a", "b", "c", "d", "e"};
Stream<String> arrayStream = Arrays.stream(arrays);
③ Stream的靜態(tài)方法:of()、iterate()、generate()
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(3);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println)
3、Stream數(shù)據(jù)過(guò)濾操作
filter:篩選,是按照一定的規(guī)則校驗(yàn)流中的元素,將符合條件的元素提取到新的流中的操作
List<UserInfo> userInfoList = builderData();
userInfoList.stream().filter(o -> o.getAge() > 20).forEach(System.out::println);
4、映射操作(map、flatMap、peek)
① map 一個(gè)元素類型為 T 的流轉(zhuǎn)換成元素類型為 R 的流,這個(gè)方法傳入一個(gè)Function的函數(shù)式接口,接收一個(gè)泛型T,返回泛型R,map函數(shù)的定義,返回的流,表示的泛型是R對(duì)象;
userInfoList.stream().map(UserInfo::getAge).forEach(System.out::println);
② flatMap 接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流,然后把所有流連接成一個(gè)流。
總之:與Map功能類似,區(qū)別在于將結(jié)合A的流轉(zhuǎn)換成B流;
List<String> list1 = Arrays.asList("h,e,l,l", "1,2,3,4");
List<String> list2 = list1.stream().flatMap(o -> {
String[] split = o.split(",");
return Arrays.stream(split);
}).collect(Collectors.toList());
System.out.println("處理前:" + list1);
System.out.println("處理后:" + list2);
③ peek 操作 一般用于不想改變流中元素本身的類型或者只想元素的內(nèi)部狀態(tài)時(shí);
而 map 則用于改變流中元素本身類型,即從元素中派生出另一種類型的操作。
Stream<String> stream = Stream.of("hello", "felord.cn");
stream.peek(System.out::println).collect(Collectors.toList());
④ 另外還有mapToInt、mapToLong、mapToDouble、flatMapToDouble、flatMapToInt、flatMapToLong
以上這些操作是map和flatMap的特例版,也就是針對(duì)特定的數(shù)據(jù)類型進(jìn)行映射處理
5、去重、排序、獲取指定個(gè)數(shù)操作
① distinct:返回由該流的不同元素組成的流,
<font color = 'red'>
1、根據(jù) Object.equals(Object)),distinct 使用hashCode()和equals()方法獲取不同元素。
2、我們的類必須實(shí)現(xiàn)hashCode()和equals()方法
</font>
② sorted:返回由該流的元素組成的流,并根據(jù)自然順序排序,該接口有兩種形式:無(wú)參和有參數(shù),如:
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
<font color='red'>區(qū)別:傳入比較器的參數(shù),可以自定義這個(gè)比較器,即自定義比較規(guī)則。</font>
③ limit:獲取流中n個(gè)元素返回的流
List<UserInfo> userInfoList = builderData();
userInfoList.stream().limit(3).forEach(System.out::println);
6、anyMatch:、allMatch、noneMatch、findFirst、findAny
① anyMatch:Stream 中只要有一個(gè)元素符合傳入的 predicate,返回 true;
boolean anyMatch(Predicate<? super T> predicate);
② allMatch:Stream 中全部元素符合傳入的 predicate,返回 true;
boolean allMatch(Predicate<? super T> predicate);
③ noneMatch:Stream 中沒(méi)有一個(gè)元素符合傳入的 predicate,返回 true.
boolean noneMatch(Predicate<? super T> predicate);
④ findFirst:用于返回滿足條件的第一個(gè)元素(但是該元素是封裝在Optional類中)
Optional<T> findFirst();
⑤ findAny:返回流中的任意元素(但是該元素也是封裝在Optional類中)
Optional<T> findAny();
<font color='red'>
findAny()操作,返回的元素是不確定的,使用findAny()是為了更高效的性能
</font>
// 以上操作使用示例
List<UserInfo> userInfoList = builderData();
boolean anyMatch = userInfoList.stream().anyMatch(o -> o.getAge() >18);
boolean allMatch = userInfoList.stream().allMatch(o -> o.getAge() >18);
boolean noneMatch = userInfoList.stream().noneMatch(o -> o.getAge() >18);
System.out.println(anyMatch+","+allMatch+","+noneMatch);
userInfoList.stream().filter(o -> o.getAge() > 35).findFirst().ifPresent(System.out::println);
System.out.println(userInfoList.stream().filter(o -> o.getAge() > 20).findAny().get());
7、foreach、forEachOrdered
注意里面的變量必須為final申明,因?yàn)閘ambda中,使用的外部變量必須是最終的,不可變的
① forEach:該方法接收一個(gè)Lambda表達(dá)式,然后在Stream的每一個(gè)元素上執(zhí)行該表達(dá)式
void forEach(Consumer<? super T> action);
② forEachOrdered:該方法接收一個(gè)Lambda表達(dá)式,然后按順序在Stream的每一個(gè)元素上執(zhí)行該表達(dá)式,并發(fā)環(huán)境下仍然不能保證順序
void forEachOrdered(Consumer<? super T> action);
③ reduce:方法接收一個(gè)函數(shù)作為累加器,數(shù)組中的每個(gè)值(從左到右)開(kāi)始縮減,最終計(jì)算為一個(gè)值
8、終止操作collect:稱為收集器,是一個(gè)終端操作,它接收的參數(shù)是將流中的元素累積到匯總結(jié)果的各種方式
| 方法 | 含義說(shuō)明 |
|---|---|
| toList | 將流中的元素收集到一個(gè)List中 |
| toSet | 將流中的元素收集到一個(gè)Set中 |
| toCollection | 將流中的元素收集到一個(gè)Collection中 |
| toMap | 將流中的元素映射收集到一個(gè)Map中 |
| counting | 統(tǒng)計(jì)流中的元素個(gè)數(shù) |
| summingInt | 計(jì)算流中指定int字段的累加總和。針對(duì)不同類型的數(shù)字類型,有不同的方法,比如summingDouble等 |
| averagingInt | 計(jì)算流中指定int字段的平均值。針對(duì)不同類型的數(shù)字類型,有不同的方法,比如averagingLong等 |
| joining | 將流中所有元素(或者元素的指定字段)字符串值進(jìn)行拼接,可以指定拼接連接符,或者首尾拼接字符 |
| maxBy | 根據(jù)給定的比較器,選擇出值最大的元素 |
| minBy | 根據(jù)給定的比較器,選擇出值最小的元素 |
| groupingBy | 根據(jù)給定的分組函數(shù)的值進(jìn)行分組,輸出一個(gè)Map對(duì)象 |
| partitioningBy | 根據(jù)給定的分區(qū)函數(shù)的值進(jìn)行分區(qū),輸出一個(gè)Map對(duì)象,且key始終為布爾值類型 |
| collectingAndThen | 包裹另一個(gè)收集器,對(duì)其結(jié)果進(jìn)行二次加工轉(zhuǎn)換 |
| reducing | 從給定的初始值開(kāi)始,將元素進(jìn)行逐個(gè)的處理,最終將所有元素計(jì)算為最終的1個(gè)值輸出 |
下面對(duì)幾個(gè)容易混淆的進(jìn)行舉例使用
① partitioningBy 使用,將數(shù)據(jù)按照條件分成兩組
Map<Boolean, List<UserInfo>> booleanListMap = userInfoList.stream().collect(Collectors.partitioningBy(item -> item.getAge() > 27));
② collectingAndThen 使用,包裹收集器
Integer maxAge = userInfoList.stream().collect(Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(UserInfo::getAge)), Optional::get)).getAge();
③ reducing使用
userInfoList.stream().map(o -> new BigDecimal(String.valueOf(o.getAge()))).collect(Collectors.reducing(BigDecimal.ZERO,BigDecimal::add));