編程中操作集合數(shù)據(jù)是非常頻繁的,使用Java8 中的Stream對集合處理,結(jié)合Lambda函數(shù)式編程能極大的簡化代碼,合理的使用Stream能提高代碼可讀性,另一方面從Java8面世以來Stream API經(jīng)過了無數(shù)項目的實踐考驗,其穩(wěn)定性和性能自不必說,網(wǎng)上有很多相關(guān)的性能測試案例可以查閱參考,如果有人對你說:Lambda 可讀性不好,維護(hù)成本高等一些問題,你大可放心,請一定看下最后的注意點
1. Stream 創(chuàng)建
Stream的創(chuàng)建方式比較多,接下來介紹幾種常用的方式,以下Lists使用的google guava的API,直接上代碼:
// 方式1:Stream.of以及其他的靜態(tài)方法,測試常使用
Stream<String> stream1 = Stream.of("A", "B");
// 方式2:Collection方式,常見如(List、Set)
Stream<String> stream2 = Lists.newArrayList("A", "B").stream();
Stream<String> stream3 = Sets.newHashSet("A", "B").stream();
// 方式3:數(shù)組方式
Stream<String> stream4 = Arrays.stream(new String[]{"A", "B"});
// 方式4:通過API接口創(chuàng)建,文件API等
Stream<String> stream5 = Files.lines(Paths.get("/file.text"));
// 方式5:創(chuàng)建基本數(shù)據(jù)類型對應(yīng)的Stream,如:IntStream、LongStream、DoubleStream
IntStream stream6 = Arrays.stream(new int[] { 1, 2, 3 });
// 方式6:通過Stream.builder創(chuàng)建
Stream<Object> stream7 = Stream.builder().add("A").build();
以上創(chuàng)建方式方式2、方式3比較常用,其中方式3也可以使用parallelStream創(chuàng)建并行流,其他的方式可以通過parallel方法轉(zhuǎn)換為并行流,在數(shù)據(jù)量較大時提高數(shù)據(jù)處理效率,如下:
// 直接使用parallelStream創(chuàng)建
Stream<String> stream1 = Lists.newArrayList("A", "B").parallelStream();
// 使用parallel轉(zhuǎn)化普通流為并行流
Stream<String> stream2 = Arrays.stream(new String[]{"A", "B"}).parallel();
2. Stream 中間操作
Stream.map
將原數(shù)據(jù)處理后生成新的數(shù)據(jù),其中mapToInt、mapToLong、mapToDouble方法可直接轉(zhuǎn)換為IntStream、LongStream、DoubleStream(用的比較少,大家可自行查找)
// 原數(shù)據(jù)添加后綴-N
List<String> result1 = Lists.newArrayList("A")
.stream().map(item -> item + "-N").collect(Collectors.toList());
// 原字符串轉(zhuǎn)化為數(shù)組
List<String[]> result2 = Lists.newArrayList("A")
.stream().map(item -> new String[]{item}).collect(Collectors.toList());
Stream.flatMap
合并多個Stream為一個Stream,經(jīng)常用在合并多個List數(shù)據(jù)
List<String> result = Lists.newArrayList(
Lists.newArrayList("A"),
Lists.newArrayList("B")
).stream().flatMap(Collection::stream).collect(Collectors.toList());
Stream.filter
元素過濾,可替代循環(huán)中的if判斷條件,參數(shù)為邏輯表達(dá)式
List<String> result = Lists.newArrayList("A", "B")
.stream().filter("A"::equals).collect(Collectors.toList());
Stream.distinct
元素去重復(fù),一般用在簡單數(shù)據(jù)類型,如果是對象可以利用TreeSet去重示例如下
// 簡單數(shù)據(jù)類型去重
List<String> result1 = Lists.newArrayList("A", "A", "B")
.stream().distinct().collect(Collectors.toList());
// 對象數(shù)據(jù)去重
List<Demo> result2 = Lists.newArrayList(new Demo()).stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(() ->
new TreeSet<>(comparing(Demo::getName))), ArrayList::new)
);
@Data
class Demo {
private String name;
private String age;
}
Stream.peek
只進(jìn)行數(shù)據(jù)處理,不改變原數(shù)據(jù)類型,和map的區(qū)別就是peek接受一個無返回值的操作,一般用于修改對象內(nèi)部元素
List<Demo> result = Lists.newArrayList(new Demo())
.stream().peek(item -> item.setName("A")).collect(Collectors.toList());
Stream.sorted
對數(shù)據(jù)進(jìn)行排序,支持正序和倒序,并且支持對象類型數(shù)據(jù)排序
// 簡單數(shù)據(jù)類型排序
List<String> result1 = Lists.newArrayList("A", "B")
.stream().sorted().collect(Collectors.toList());
// 對象類型根據(jù)某個屬性排序,默認(rèn)正序,倒序使用reversed方法
List<Demo> result2 = Lists.newArrayList(new Demo())
.stream().sorted(Comparator.comparing(Demo::getName).reversed()).collect(Collectors.toList());
Stream.limit
限制最終輸出數(shù)據(jù)的數(shù)量,截取流中的元素,默認(rèn)不進(jìn)行截取
List<String> result1 = Lists.newArrayList("A", "B")
.stream().limit(1).collect(Collectors.toList());
Stream.skip
跳過前多少個元素,和limit類似,limit是截取流達(dá)到限制數(shù)量立刻返回流
List<String> result = Lists.newArrayList("A", "B")
.stream().skip(1).collect(Collectors.toList());
3. Stream 終止操作
collect
收集流數(shù)據(jù),常用:Collectors.toList(收集為List)、Collectors.joining(收集拼接為String)
// 收集數(shù)據(jù)為List
List<String> result1 = Lists.newArrayList("A", "B").stream().collect(Collectors.toList());
// 收集數(shù)據(jù)為String,默認(rèn)無分隔符,可以使用帶參數(shù)的joining方法指定分隔符
String result2 = Lists.newArrayList("A", "B").stream().collect(Collectors.joining());
reduce
數(shù)據(jù)聚合為一個值,數(shù)據(jù)轉(zhuǎn)化為單值后,計算得出一個最終值,這里已累加為例
BigDecimal result = Lists.newArrayList(BigDecimal.valueOf(1), BigDecimal.valueOf(2)).stream().reduce(BigDecimal.ZERO, BigDecimal::add);
allMatch、anyMatch、noneMatch
// 所有元素都大于1,返回true
boolean result1 = Lists.newArrayList(1, 2, 3, 4).stream().allMatch(item -> item > 1);
// 任意元素大于1,返回true
boolean result2 = Lists.newArrayList(1, 2, 3, 4).stream().anyMatch(item -> item > 1);
// 沒有元素大于1,返回true
boolean result3 = Lists.newArrayList(1, 2, 3, 4).stream().noneMatch(item -> item > 1);
count
統(tǒng)計數(shù)據(jù)數(shù)量值
long result1 = Lists.newArrayList(1, 2, 3, 4).stream().count();
findAny、findFirst
如果存在數(shù)據(jù),都返回一條,區(qū)別是在并行處理中,findAny匹配到數(shù)據(jù)就返回,findFirst需要等所有數(shù)據(jù)處理完成返回第一條,所以在并行處理中findAny效率更高
// 獲取任意一個及時返回
Integer result1 = Lists.newArrayList(1, 2, 3, 4).stream().findAny().get();
// 所有元素執(zhí)行完成返回第一條
Integer result12= Lists.newArrayList(1, 2, 3, 4).parallelStream().findFirst().get();
forEach、forEachOrdered
遍歷所有元素,比如輸出操作,有了forEach為什么還需要forEachOrdered呢,主要是在并行執(zhí)行中,元素執(zhí)行是沒有順序的,forEachOrdered能將結(jié)果按照順序輸出
// 輸出所有元素
Lists.newArrayList(1, 2, 3, 4).stream().forEach(System.out::println);
// 順序輸出
Lists.newArrayList(1, 2, 3, 4).parallelStream().forEachOrdered(System.out::println);
max、min
獲取流中元素最大和最小的值,以下舉例最大值得獲取,最小值同理
// 簡單數(shù)據(jù)類型
Integer result = Lists.newArrayList(1, 2, 3, 4).stream().max(Integer::compare).get();
// 比較對象中的屬性,獲取最大的記錄
Demo result = Lists.newArrayList(new Demo()).stream().max(comparing(Demo::getAge)).get();
4. Stream 注意點
在使用并行流進(jìn)行處理時,一定需要收集最終數(shù)據(jù),否則可能會丟失數(shù)據(jù),比如使用collect或者reduce收集數(shù)據(jù),也就是說使用了collect和reduce才能使用parallelStream,此時整個流處理是線程安全的