Stream 流式编程

优质博文:IT-BLOG-CN

大家都知道可以将Collection类转化成流Stream进行操作(Map并不能创建流),代码变得简约流畅。我们先看下流的几个特点:

1、流并不存储元素。这些元素可能存储在底层的集合中,或者是按需生成。

2、流的操作不会修改其数据元素,而是生成一个新的流。

3、流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。

一、创建流

负责新建一个Stream流,大多数都是基于现有的数组、ListSetMap等集合创建新的Stream

stream()

创建一个stream串行流对象。

CR时可优化的代码片段:

java 复制代码
public List<SFltStudent> toListByOldTicketNo(List<SFltStudent> sourceList) {
  List<SFltStudent> targetList = Lists.newArrayListWithExpectedSize(sourceList.size());
  for (SFltStudent source : sourceList) {
    SFltStudent target = new SFltStudent();
    target.setTicketNo(source.getOldTicketNo());
    target.setFlightAgency(source.getFlightAgency());
    targetList.add(target);
  }
  return targetList;
}

代码优化:这里sourceList如果数据量很大时,也可以考虑parallel stream。这里主要是想通过stream提高代码简洁性和可读性。

java 复制代码
public List<SFltStudent> toListByOldTicketNo(List<SFltStudent> sourceList) {
    return sourceList.stream()
            .map(source -> {
                SFltStudent target = new SFltStudent();
                target.setTicketNo(source.getOldTicketNo());
                target.setFlightAgency(source.getFlightAgency());
                return target;
            })
            .collect(Collectors.toList());
}

parallelStream()

创建一个可并行执行的stream流对象。可以有效利用计算机的多CPU硬件,提升逻辑的执行速度。将一整个stream划分为多个片段,然后对各个分片流并行执行处理逻辑,最后将各个分片流的执行结果汇总为一个整体流。

::: tip

如果遇到耗时的操作,或者大量IO的操作,或者有线程sleep的操作一定要避免使用并行流。

并行流场景效率会比迭代器逐个循环更高。

:::

查看parallelStream的源码发现parallel Stream底层是将任务进行了切分,最终将任务传递给了jdk8自带的"全局"ForkJoinPool线程池。在Fork-Join中,比如一个拥有4个线程的ForkJoinPool线程池,有一个任务队列,一个大的任务切分出的子任务会提交到线程池的任务队列中,4个线程从任务队列中获取任务执行,哪个线程执行的任务快,哪个线程执行的任务就多,只有队列中没有任务线程才是空闲的,这就是工作窃取。

java 复制代码
/**
 * @return a possibly parallel {@code Stream} over the elements in this == parallelStream()并不一定返回一个并行流,有可能parallelStream()全是由主线程顺序执行的。
 * collection
 * @since 1.8
 */
default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

注意:parallelStream和整个java进程共用ForkJoinPool:如果直接使用parallelStream().foreach会默认使用全局的ForkJoinPool,而这样就会导致当前程序很多地方共用同一个线程池,包括gc相关操作在内,所以一旦任务队列中满了之后,就会出现阻塞的情况,导致整个程序的只要当前使用ForkJoinPool的地方都会出现问题。

CR时可优化的代码片段: :并发获取接口数据,进行业务处理,对共享数据的修改需要考虑多线程安全问题。

java 复制代码
List<String> errorMessageList = Collections.synchronizedList(new ArrayList<>());
List<String> errorProductOrderIds = Collections.synchronizedList(new ArrayList<>());

infos.parallelStream()
    .filter(XStudentOrderInfo::getChecked)
    .map(XStudentOrderInfo::getProductOrderID)
    .filter(StringUtils::isNotBlank)
    .distinct()
    .allMatch(productOrderId -> {
        XRefundResponse response = xStudentCancelSoa.xStudentClassOrder(getXStudentRequest(eid, refundInfo, productOrderId));
        boolean isSuccess = response.getResponseStatus() != null
            && response.getResponseStatus().ack == AckCodeType.Success
            && response.isIsSuccess() != null
            && response.isIsSuccess();
        if (!isSuccess && StringUtils.isNotBlank(response.getMessage())) {
            errorMessageList.add(response.getMessage());
            errorProductOrderIds.add(productOrderId);
        }
        return isSuccess;
    })
);

代码优化:将复杂的条件判断提取到processOrder方法中,使主流处理逻辑更加简洁和易读。

java 复制代码
List<String> errorMessageList = Collections.synchronizedList(new ArrayList<>());
List<String> errorProductOrderIds = Collections.synchronizedList(new ArrayList<>());

boolean allSuccess = infos.parallelStream()
    .filter(XStudentOrderInfo::getChecked)
    .map(XStudentOrderInfo::getProductOrderID)
    .filter(StringUtils::isNotBlank)
    .distinct()
    .allMatch(productOrderId -> processOrder(productOrderId, errorMessageList, errorProductOrderIds));

private boolean processOrder(String productOrderId, List<String> errorMessageList, List<String> errorProductOrderIds) {
    XRefundResponse response = xStudentCancelSoa.xStudentClassOrder(getXStudentRequest(eid, refundInfo, productOrderId));
    boolean isSuccess = response.getResponseStatus() != null
            && response.getResponseStatus().ack == AckCodeType.Success
            && Boolean.TRUE.equals(response.isIsSuccess());
    if (!isSuccess && StringUtils.isNotBlank(response.getMessage())) {
        errorMessageList.add(response.getMessage());
        errorProductOrderIds.add(productOrderId);
    }
    return isSuccess;
}

Stream.of()

通过给定的一系列元素创建一个新的stream串行流对象。

二、Stream 中间处理

输入Stream对象,输出一个新的Stream对象,中间管道操作可以进行叠加。

规范

CR时发现不规范的流式编程如下:

java 复制代码
issueBillList.stream().map(IssueBillDO::getIssueBillId).collect(Collectors.toList());

根据代码规范,在代码中使用链式调用时,为了提高代码的可读性和维护性,建议在方法链的每个方法调用之间进行换行。这样可以使代码更容易阅读和理解。

java 复制代码
List<Long> issueBillIds = issueBillList.stream()
                                       .map(IssueBillDO::getIssueBillId)
                                       .collect(Collectors.toList());

filter()

按照条件过滤符合要求的元素,返回新的stream流。

CR时可优化的代码片段: .filter多个过滤条件并存,存在一定的优化空间。编程如下:

java 复制代码
.filter(r -> StringUtilsExt.compareIgnoreSpaceAndCaps(r.getPassengerName(), trace.getPassengerName())
            && StringUtilsExt.compareIgnoreSpaceAndCaps(r.getFlight(), trace.getFlightNo())
            && StringUtilsExt.compareIgnoreSpaceAndCaps(r.getDPort(), trace.getDport()))
            ......

建议根据业务将它们拆分为多个.filter方法调用可以提高代码的可读性和可维护性。但是需要注意每个.filter调用都会遍历一次流中的元素。如果流非常大,多个.filter调用可能会带来性能开销。同时如果条件之间存在逻辑依赖关系,拆分成多个.filter调用可能会导致逻辑错误。例如,如果某个条件的结果会影响另一个条件的判断,拆分可能会破坏这种依赖关系。虽然拆分可以提高某些情况下的可读性,但如果条件本身很简单,拆分反而会使代码显得冗长和复杂。

具体大家根据自己的业务特点进行选择

方案一:如果条件非常复杂,或者你希望每个条件都能单独清晰地表达,可以拆分成多个.filter方法

java 复制代码
.filter(r -> StringUtilsExt.compareIgnoreSpaceAndCaps(r.getPassengerName(), trace.getTripInfo().getPassengerName()))
.filter(r -> StringUtilsExt.compareIgnoreSpaceAndCaps(r.getFlight(), trace.getTripInfo().getFlightNo()))
.filter(r -> StringUtilsExt.compareIgnoreSpaceAndCaps(r.getDPort(), trace.getTripInfo().getDport()))

方案二:如果条件逻辑非常复杂,考虑将条件封装到一个辅助方法中,这样代码会更加清晰

java 复制代码
.filter(r -> matchesTraceInfo(r, trace.getTripInfo()))

private boolean matchesTraceInfo(Record r, TripInfo tripInfo) {
    return StringUtilsExt.compareIgnoreSpaceAndCaps(r.getPassengerName(), tripInfo.getPassengerName()) &&
           StringUtilsExt.compareIgnoreSpaceAndCaps(r.getFlight(), tripInfo.getFlightNo()) &&
           StringUtilsExt.compareIgnoreSpaceAndCaps(r.getDPort(), tripInfo.getDport());
}

map()

将已有元素转换为另一个对象类型,一对一逻辑,返回新的stream流。

java 复制代码
List<String> ids = Arrays.asList("A1", "A2", "A3");
        // 使用流操作
List<String> results = ids.stream()
        .map(id -> {
            id.replace("A","B");
            return id;
        })
        .collect(Collectors.toList());
System.out.println(results);

执行之后,会发现每一个元素都被转换为对应新的元素,但是前后总元素个数是一致的:

java 复制代码
B1
B2
B3

下面的代码因对mapfilter功能的混淆,导致代码执行解决与预期不符,最终出现生产故障。

java 复制代码
if (response  != null && response.isPresent() && response.isPresent().get().getResult() != null) {
        ResultType resultType = response.isPresent().get().getResult();
        resultType.getResultList().stream()
                                .map(p -> matchChildResult(p) && p.getCode == CODE_404)
                                .findFirst().ifPresent(result -> {
                                    logger.build("childdata", "fail:).info();
                                    if (ConfigFunc.getBoolean("childIntercept", false)) {
                                        throw new ResultException("fail);
                                    }
                                });

原因:如果使用map这段代码会返回一个List<boolean>的列表,应该不是开发者想要的。而且,只要respose返回了结果,那么map就会返回一个List<boolean>列表,这个列表可能为:[true,false,......]等等,开发者应该要的是满足条件才抛出错误的,但是生产应该是只要respose返回了结果code无论是不是404都会抛错。导致线上系统异常,订单下跌。

flatMap()

将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流。

案例:

java 复制代码
List<String> sentences = Arrays.asList("B1 B2","B3 B4");
// 使用流操作
List<String> results2 = sentences.stream()
        .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
        .collect(Collectors.toList());
System.out.println(results2);

执行之后,会发现每一个元素都被转换为多个新的元素:

java 复制代码
B1
B2
B3
B4

flatMap操作是先将每个元素进行处理并返回一个新的Stream,然后将多个Stream展开合并为了一个完整的新的Stream,如下:

CR时可优化的代码片段: 应用场景为List中的对象中包含List列表

java 复制代码
List<SpecialEventMaterialInfo> allMaterialList = specialEventInfoForPageList.stream()
    .filter(Objects::nonNull)
    .filter(p -> CollectionUtils.isNotEmpty(p.getMaterialInfoList()))
    .flatMap(p -> p.getMaterialInfoList().stream().filter(Objects::nonNull))
    .collect(Collectors.toList());

代码优化:提前检查p.getMaterialInfoList()是否为空的处理,CollectionUtilsCollectors被频繁使用,可以进行静态导入以简化代码。

java 复制代码
List<SpecialEventMaterialInfo> allMaterialList = specialEventInfoForPageList.stream()
    .filter(p -> p != null && isNotEmpty(p.getMaterialInfoList()))
    .flatMap(p -> p.getMaterialInfoList().stream())
    .filter(Objects::nonNull)
    .collect(toList());

limit()

仅保留集合前面指定个数的元素,返回新的stream流。

java 复制代码
Stream<Integer> integerStream = Arrays.stream({1, 2, 3})
                                      .limit(2);
System.out.println(Arrays.toString(integerStream.toArray())); // [1, 2]

skip()

跳过集合前面指定个数的元素,返回新的stream流。

java 复制代码
Stream<Integer> integerStream = Arrays.stream({1, 2, 3});
                                      .skip(2);
System.out.println(Arrays.toString(integerStream.toArray())); // [3]

concat()

将两个流的数据合并起来为1个新的流,返回新的stream流。

distinct()

Stream中所有元素进行去重,返回新的stream流。

**CR`时可优化的代码片段:**

java 复制代码
submitReiEntityList = model.getReibursementInfo().getSubmitReiEntityList().stream()
    .map(ReibursementApplyOrderInfo::getOrderId)
    .distinct()
    .collect(Collectors.toList());

这里主要说一个思想,是否可以将需要distinct的集合转换为Set进行存储,提高查找效率。

sorted()

stream中所有的元素按照指定规则进行排序,返回新的stream流。

这里主要看一下目前存在的写法

CR片段一

java 复制代码
wordSet1 = wordSet.stream().sorted(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o2.length() - o1.length();
    }
}).collect(Collectors.toList());

CR片段二

java 复制代码
List<RescheduleLog> sortedLogs = logs.stream()
    .sorted((RescheduleLog i1, RescheduleLog i2) -> i2.getRecordTime().compareTo(i1.getRecordTime()))
    .collect(Collectors.toList());

CR片段三:上面的片段可以按照该规范,简化代码。

java 复制代码
List<RescheduleIssueBill> orderedDescList = rescheduleIssueBills.stream()
    .sorted(Comparator.comparing(RescheduleIssueBill::getIssueBillID).reversed())
    .collect(Collectors.toList());

CR片段四

java 复制代码
List<RescheduleIssueBill> orderedDescList = rescheduleIssueBills
    .stream()
    .sorted(Comparator.comparing(RescheduleIssueBill::getIssueBillID).reversed())
    .collect(Collectors.toList());

代码优化:如果不需要保留原始列表的顺序,可以直接对original进行排序,避免创建额外的心列表。

java 复制代码
original.sort(Comparator.comparing(SegmentInfo::getSortedSequence));

peek()

stream流中的每个元素进行逐个遍历处理,返回处理后的stream流。意味着peek只能作为管道中途的一个处理步骤,而没法直接执行得到结果,其后面必须还要有其它终止操作的时候才会被执行;而foreach作为无返回值的终止方法,则可以直接执行相关操作。

CR过程中使用peek的代码,peek么有问题,但是代码还是有一定的优化空间。

java 复制代码
List<AllianceAuditInfo> auditSuccessList = auditInfoList.stream()
    .filter(auditInfo -> AllianceAuditStatusEnum.AUDIT_SUCCESS.getValue().equals(auditInfo.getAuditStatus()))
    .peek(auditInfo -> {
        Integer customKey = idxAtomic.getAndUpdate(idx -> idx + NumberUtils.INTEGER_ONE);
        auditInfo.setCustomKey(customKey);
    })
    .collect(Collectors.toList());

我们给一个更优雅的代码:

java 复制代码
List<AllianceAuditInfo> auditSuccessList = auditInfoList.stream()
    .filter(auditInfo -> AllianceAuditStatusEnum.AUDIT_SUCCESS.getValue().equals(auditInfo.getAuditStatus()))
    .peek(auditInfo -> auditInfo.setCustomKey(idxAtomic.getAndIncrement()))
    .collect(Collectors.toList());

三、终止Stream

通过终止管道操作之后,Stream流将会结束,最后可能会执行某些逻辑处理,或者是按照要求返回某些执行后的结果数据。

count()

返回stream处理后最终的元素个数。

CR时可优化的代码片段:

java 复制代码
groupByDataType.entrySet().stream()
    .allMatch(entry -> entry.getValue().stream()
        .map(DiscountInfo::getDeductionAmount)
        .distinct()
        .count() == 1);

代码优化:上述代码distinctcount结合使用时,可以使用Setlength()方法实现,但是这里使用countdistinct可能从业务上理解更为接近,所以具体需要根据业务场景决定。

java 复制代码
boolean allMatch = groupByDataType.entrySet().stream()
    .allMatch(entry -> entry.getValue().stream()
        .map(DiscountInfo::getDeductionAmount)
        .collect(Collectors.toSet())
        .size() == 1);

但是这里可以根据allMatch的特性上进行优化,只要找到一个不满足条件的金额,就提前返回false提交性能。

java 复制代码
boolean allMatch = groupByDataType.entrySet().stream()
    .allMatch(entry -> {
        Set<BigDecimal> deductionAmounts = entry.getValue().stream()
            .map(DiscountInfo::getDeductionAmount)
            .collect(Collectors.toSet());
        return deductionAmounts.size() == 1;
    });

max()

返回stream处理后的元素最大值。

CR时可优化的代码片段:

java 复制代码
files.stream()
     .mapToInt(UploadRetireMaterialInfoType::getBatchNo)
     .max()
     .getAsInt();

代码优化:这里主要的问题是,再调用getAsInt()方法时,一定要判断下是否存在,否则回报异常。

java 复制代码
OptionalInt maxBatchNoOptional = files.stream()
            .mapToInt(UploadRetireMaterialInfoType::getBatchNo)
            .max();

        if (maxBatchNoOptional.isPresent()) {
            int maxBatchNo = maxBatchNoOptional.getAsInt();
        } else {
            ......
        }

min()

返回stream处理后的元素最小值。

CR过程中发现可以使用min()方法进行优化的代码片段

java 复制代码
List<SFltticketStudentByairlineMy> sortRefundDetails = refundDetails.stream()
    .sorted(Comparator.comparing(SFltticketStudentByairlineMy::getSequence))
    .collect(toList());
SFltticketStudentByairlineMy firstSeqTicketNo = sortRefundDetails.get(0);

优化后代码如下:

java 复制代码
refundDetails.stream()
    .min(Comparator.comparing(SFltticketStudentByairlineMy::getSequence));

findFirst()

找到第一个符合条件的元素时则终止流处理。

优化片段一:

CR时发现.findFirst()返回Optional可以继续进行业务处理,存在一定的优化空间。代码如下:

java 复制代码
oc.getOrderInfoList().stream()
    .filter(f -> (StringUtilsExt.compareIgnoreSpaceAndCaps(f.getFlight(), lastTrip.getFlightNo())
            ......)
    .findFirst().orElse(null);
    if (lastFlight != null) {
        ......
    }

可以在findFirst()方法后继续执行操作,而不需要单独的if (lastFlight != null)语句。流式编程提供了ifPresent方法,可以让你在找到符合条件的元素时执行某些操作。这样使代码更加简洁和流畅,不需要显式地进行空值检查。

java 复制代码
oc.getOrderInfoList().stream()
    .filter(f -> (StringUtilsExt.compareIgnoreSpaceAndCaps(f.getFlight(), lastTrip.getFlightNo())
            ......)
    .findFirst()
    .ifPresent(lastFlight -> {
        // 在这里执行你需要的操作
        // 例如:
        // System.out.println("Found flight: " + lastFlight);
    });

优化片段二:

.findFirst()方法使用存在优化空间

java 复制代码
List<SFltticketStudentByairlineMy> sortRefundDetails = refundDetails.stream()
    .sorted(Comparator.comparing(SFltticketStudentByairlineMy::getSequence))
    .collect(toList());
SFltticketStudentByairlineMy firstSeqTicketNo = sortRefundDetails.get(0);

使用.findFirst()方法获取第一个符合要求的元素即可。当然这个代码还存在优化空间。

java 复制代码
SFltticketStudentByairlineMy firstSeqTicketNo = refundDetails.stream()
    .sorted(Comparator.comparing(SFltticketStudentByairlineMy::getSequence))
    .collect(toList())
    .findFirst();

findAny()

找到任何一个符合条件的元素时则退出流处理,这个对于串行流时与findFirst相同,对于并行流时比较高效,任何分片中找到都会终止后续计算逻辑。

CR时可优化的代码片段:

java 复制代码
orderInfo.getRefundInfoList().stream()
    .filter(a -> MATERIAL_SUPPLEMENT_FLAG.equals(a.getKey()) && TRUE_VALUE.equals(a.getValue()))
    .findAny()
    .isPresent();

优化代码:返回的是一个boolean类型,可以直接使用anyMatch()

java 复制代码
boolean isPresent = orderInfo.getRefundOrderFlagInfoList().stream()
    .anyMatch(a -> MATERIAL_SUPPLEMENT_FLAG.equals(a.getKey()) && TRUE_VALUE.equals(a.getValue()));

anyMatch()

返回一个boolean值,类似于isContains(),用于判断是否有符合条件的元素。

我们也会将写的标准的代码推荐给大家

java 复制代码
boolean isAgencyModeOrder = CollectionsUtil.isNotEmpty(orderAlibabaCartList) 
        && orderAlibabaCartList.stream()
                                .filter(s -> Objects.equals(s.getBookType(), BookingTypeConstants.TICKET_PLUS_X_ORDER))
                                .anyMatch(s -> Objects.equals(s.getPaymentVersion(), PaymentVersionConstants.PAYMENT_AGENCY));

allMatch()

返回一个boolean值,用于判断是否所有元素都符合条件。

CR中发现可以优化的代码:在流操作中fucLi部分存在优化空间。

java 复制代码
private Stream<AllianceAuditDTO> doFilter(List<AllianceAuditDTO> sourceList) {
    return sourceList.stream()
            .filter(
                    source -> {
                        List<Supplier<Boolean>> fucLi =
                                buildFilterConditions(source);
                        return fucLi.stream().allMatch(Supplier::get);
                    });
}

代码是一个过滤方法,它将一个List<AllianceAuditDTO>转换为一个Stream<AllianceAuditDTO>,并根据某些条件对其进行过滤。具体来说,它使用了buildFilterConditions方法来生成一组Supplier<Boolean>,然后检查这些条件是否都满足。如果所有条件都满足,则保留该元素。

优化后的代码:将fucLi变量内联到filter方法中,减少了不必要的局部变量声明,使代码更加简洁。

java 复制代码
private Stream<AllianceAuditDTO> doFilter(List<AllianceAuditDTO> sourceList) {
    return sourceList.stream()
            .filter(source -> buildFilterConditions(source).stream().allMatch(Supplier::get));
}

noneMatch()

返回一个boolean值, 用于判断是否所有元素都不符合条件。

CR时可优化的代码片段:

java 复制代码
boolean userBehaviorsCheck = filterRecordList.stream().noneMatch(record -> IntegerUtils.compare(record.getPageCode(), 201));

collect()

将流转换为指定的类型,通过Collectors进行指定。

toArray()

将流转换为数组。

iterator()

将流转换为Iterator对象。

CR时可优化的代码片段:

java 复制代码
Iterator<M_RelateAliPassenger> iterator = passengers.iterator();
while (iterator.hasNext()) {
    M_RelateAliPassenger passenger = iterator.next();
    boolean matched = passengers2.stream()
        .anyMatch(p -> p.getPassengerName() != null && p.getPassengerName().equalsIgnoreCase(passenger.getPassengerName()));
    if (!matched) {
        iterator.remove();
    }
}

优化后的代码:主要任务是从passengers列表中移除那些在passengers2列表中没有匹配的乘客。可以通过集合操作来简化和优化这段代码。

java 复制代码
passengers.removeIf(passenger ->
    passengers2.stream()
        .noneMatch(p -> p.getPassengerName() != null
            && p.getPassengerName().equalsIgnoreCase(passenger.getPassengerName()))
);

foreach()

无返回值,对元素进行逐个遍历,然后执行给定的处理逻辑。foreach()操作与parallelStream()搭配使用时,必须保证是线程安全的。也不要直接使用默认的线程池。

CR时可优化的代码片段:

java 复制代码
parameterList.forEach(param -> orderIds.append(param.getOrderID()).append(","));

优化后的代码:Collectors.joining(",")最适合做上述的工作,应该是首先想到的。

java 复制代码
String orderIds = parameterList.stream()
    .map(param -> param.getOrderID())
    .collect(Collectors.joining(","));

常见问题

一旦一个Stream被执行了终止操作之后,后续便不可以再读这个流执行其他的操作了,否则会报错,看下面示例:

java 复制代码
public void testHandleStreamAfterClosed() {
    List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
    Stream<String> stream = ids.stream().filter(s -> s.length() > 2);
    // 统计stream操作后剩余的元素个数
    System.out.println(stream.count());
    System.out.println("-----下面会报错-----");
    // 判断是否有元素值等于205
    try {
        System.out.println(stream.anyMatch("205"::equals));
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println(e.toString());
    }
    System.out.println("-----上面会报错-----");
}

结果:

java 复制代码
-----下面会报错-----
java.lang.IllegalStateException: stream has already been operated upon or closed
-----上面会报错-----
java.lang.IllegalStateException: stream has already been operated upon or closed
  at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
  at java.util.stream.ReferencePipeline.anyMatch(ReferencePipeline.java:516)
  at Solution_0908.main(Solution_0908.java:55)

因为stream已经被执行count()终止方法了,所以对stream再执行anyMatch方法的时候,就会报错stream has already been operated upon or closed,这一点在使用的时候需要特别注意。

四、collect方法

获取一个集合类的结果对象,比如ListSet或者HashMap等。

Collectors.toList()

java 复制代码
List<NormalOfferModel> collectList = normalOfferModelList
        .stream()
        .filter(offer -> offer.getCate1LevelId().equals("11"))
        .collect(Collectors.toList());

Collectors.toSet()

java 复制代码
Set<NormalOfferModel> collectSet = normalOfferModelList
        .stream()
        .filter(offer -> offer.getCate1LevelId().equals("22"))
        .collect(Collectors.toSet());

Collectors.toMap

CodeReview 时发现的问题:没有考虑key重复问题。

java 复制代码
Arrays.stream(clazz.getDeclaredFields())
                .collect(Collectors.toMap(r -> r.getName().toLowerCase(), r -> r));

优化后的代码:Function.identity()java.util.function.Function接口中的一个静态方法。它总是返回一个其输入参数的函数。这在需要传递一个不做任何变换的函数时非常有用。Function.identity()等价于上面的r -> r(k1, k2) -> k2就是解决重复key的问题,当存在重复key时使用最后一个key

java 复制代码
Arrays.stream(clazz.getDeclaredFields())
                .collect(NormalOfferModel::getName, Function.identity(), (k1, k2) -> k2));

Collectors.joining

java 复制代码
List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
String joinResult = ids.stream().collect(Collectors.joining(","));

Collectors.averagingInt

java 复制代码
List<Integer> ids = Arrays.asList(10, 20, 30, 40, 50);
// 计算平均值
Double average = ids.stream().collect(Collectors.averagingInt(value -> value));

Collectors.summarizingInt

java 复制代码
List<Integer> ids = Arrays.asList(10, 20, 30, 40, 50);
// 数据统计信息
IntSummaryStatistics summary = ids.stream().collect(Collectors.summarizingInt(value -> value));

Optional 类

ifPresent(Consumer<? super T> action)

如果Optional中包含值,执行给定的Consumer操作,否则什么也不做。常用于简化代码,避免显式的空值检查。

isPresent()

检查Optional中是否包含值。如果包含值,返回true,否则返回false

get()

如果Optional中包含值,返回该值;否则抛出NoSuchElementException。这个方法不推荐频繁使用,因为它违背了Optional的初衷,即避免显式的空值检查和异常处理。

orElse(T other)

如果Optional中包含值,返回该值;否则返回other。常用于提供默认值。

orElseGet(Supplier<? extends T> other)

如果Optional中包含值,返回该值;否则通过调用Supplier获取一个默认值。与orElse不同的是,Supplier只有在需要时才会被调用,因此适用于生成默认值开销较大的情况。

isEmpty()

检查Optional中是否为空。如果为空,返回true,否则返回false

orElseThrow()

如果Optional中包含值,返回该值;否则抛出NoSuchElementException

java 复制代码
optional.orElseThrow(() -> new IllegalArgumentException("Value is absent"));

orElseThrow(Supplier<? extends X> exceptionSupplier)

如果Optional中包含值,返回该值;否则通过Supplier抛出指定的异常。

filter(Predicate<? super T> predicate)

如果Optional中包含值,并且该值满足给定的谓词,返回一个包含该值的Optional;否则返回一个空的Optional。常用于条件过滤。

java 复制代码
Optional<String> filtered = optional.filter(value -> value.length() > 3);

map(Function<? super T, ? extends U> mapper)

如果Optional中包含值,应用给定的函数并返回一个包含映射结果的Optional;否则返回一个空的Optional。常用于链式调用。

java 复制代码
Optional<Integer> length = optional.map(String::length);

flatMap(Function<? super T, Optional<U>> mapper)

map类似,但mapper函数返回的是一个Optional对象,并且不会对返回的Optional进行嵌套。

java 复制代码
Optional<String> name = optional.flatMap(value -> Optional.of("Processed " + value));
相关推荐
不会写DN1 小时前
其实跨域问题是后端来解决的? CORS
服务器·网络·面试·go
2301_813599552 小时前
Go语言怎么做秒杀系统_Go语言秒杀系统实战教程【实用】
jvm·数据库·python
NCIN EXPE6 小时前
redis 使用
数据库·redis·缓存
MongoDB 数据平台6 小时前
为编码代理引入 MongoDB 代理技能和插件
数据库·mongodb
lUie INGA6 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
极客on之路6 小时前
mysql explain type 各个字段解释
数据库·mysql
代码雕刻家7 小时前
MySQL与SQL Server的基本指令
数据库·mysql·sqlserver
lThE ANDE7 小时前
开启mysql的binlog日志
数据库·mysql
yejqvow127 小时前
CSS如何控制placeholder文字的颜色_使用--placeholder伪元素
jvm·数据库·python
oLLI PILO7 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库