Stream 流式编程

优质博文:IT-BLOG-CN

在这里插入图片描述

大家都知道可以将Collection类转化成流Stream进行操作(Map并不能创建流),代码变得简约流畅。我们先看下流的几个特点:
1、流并不存储元素。这些元素可能存储在底层的集合中,或者是按需生成。
2、流的操作不会修改其数据元素,而是生成一个新的流。
3、流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。

一、创建流

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

stream()

创建一个stream串行流对象。

CR时可优化的代码片段:

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提高代码简洁性和可读性。

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个线程从任务队列中获取任务执行,哪个线程执行的任务快,哪个线程执行的任务就多,只有队列中没有任务线程才是空闲的,这就是工作窃取。

/**
 * @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时可优化的代码片段: :并发获取接口数据,进行业务处理,对共享数据的修改需要考虑多线程安全问题。

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方法中,使主流处理逻辑更加简洁和易读。

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时发现不规范的流式编程如下:

issueBillList.stream().map(IssueBillDO::getIssueBillId).collect(Collectors.toList());

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

List<Long> issueBillIds = issueBillList.stream()
                                       .map(IssueBillDO::getIssueBillId)
                                       .collect(Collectors.toList());

filter()

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

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

.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方法

.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()))

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

.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流。

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);

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

B1
B2
B3

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

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流。

案例:

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);

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

B1
B2
B3
B4

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

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

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被频繁使用,可以进行静态导入以简化代码。

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

limit()

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

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

skip()

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

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`时可优化的代码片段:**

submitReiEntityList = model.getReibursementInfo().getSubmitReiEntityList().stream()
    .map(ReibursementApplyOrderInfo::getOrderId)
    .distinct()
    .collect(Collectors.toList());

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

sorted()

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

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

CR片段一

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

CR片段二

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

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

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

CR片段四

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

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

original.sort(Comparator.comparing(SegmentInfo::getSortedSequence));

peek()

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

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

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());

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

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时可优化的代码片段:

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

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

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

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

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时可优化的代码片段:

files.stream()
     .mapToInt(UploadRetireMaterialInfoType::getBatchNo)
     .max()
     .getAsInt();

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

OptionalInt maxBatchNoOptional = files.stream()
            .mapToInt(UploadRetireMaterialInfoType::getBatchNo)
            .max();

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

min()

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

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

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

优化后代码如下:

refundDetails.stream()
    .min(Comparator.comparing(SFltticketStudentByairlineMy::getSequence));

findFirst()

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

优化片段一:

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

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

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

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

优化片段二:

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

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

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

SFltticketStudentByairlineMy firstSeqTicketNo = refundDetails.stream()
    .sorted(Comparator.comparing(SFltticketStudentByairlineMy::getSequence))
    .collect(toList())
    .findFirst();

findAny()

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

CR时可优化的代码片段:

orderInfo.getRefundInfoList().stream()
    .filter(a -> MATERIAL_SUPPLEMENT_FLAG.equals(a.getKey()) && TRUE_VALUE.equals(a.getValue()))
    .findAny()
    .isPresent();

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

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

anyMatch()

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

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

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部分存在优化空间。

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方法中,减少了不必要的局部变量声明,使代码更加简洁。

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

noneMatch()

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

CR时可优化的代码片段:

boolean userBehaviorsCheck = filterRecordList.stream().noneMatch(record -> IntegerUtils.compare(record.getPageCode(), 201));

collect()

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

toArray()

将流转换为数组。

iterator()

将流转换为Iterator对象。

CR时可优化的代码片段:

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列表中没有匹配的乘客。可以通过集合操作来简化和优化这段代码。

passengers.removeIf(passenger ->
    passengers2.stream()
        .noneMatch(p -> p.getPassengerName() != null
            && p.getPassengerName().equalsIgnoreCase(passenger.getPassengerName()))
);

foreach()

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

CR时可优化的代码片段:

parameterList.forEach(param -> orderIds.append(param.getOrderID()).append(","));

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

String orderIds = parameterList.stream()
    .map(param -> param.getOrderID())
    .collect(Collectors.joining(","));

常见问题

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

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.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()

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

Collectors.toSet()

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

Collectors.toMap

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

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

Arrays.stream(clazz.getDeclaredFields())
                .collect(NormalOfferModel::getName, Function.identity(), (k1, k2) -> k2));

Collectors.joining

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

Collectors.averagingInt

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

Collectors.summarizingInt

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

optional.orElseThrow(() -> new IllegalArgumentException("Value is absent"));

orElseThrow(Supplier<? extends X> exceptionSupplier)

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

filter(Predicate<? super T> predicate)

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

Optional<String> filtered = optional.filter(value -> value.length() > 3);

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

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

Optional<Integer> length = optional.map(String::length);

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

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

Optional<String> name = optional.flatMap(value -> Optional.of("Processed " + value));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/872435.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VMwareWorkstation安装Kali系统教程

Kali系统&#xff0c;全名为Kali Linux&#xff0c;为渗透测试和网络安全领域提供一个全面的工具集合。Kali系统预装了各种用于渗透测试和漏洞利用的工具&#xff0c;包括端口扫描、密码破解、网络嗅探、漏洞分析等。这些工具可以帮助安全专业人员评估和测试网络的安全性&#…

实例讲解Simulink应用层开发CAN报文解包及CAN信号设置方法

在VCU应用层开发中&#xff0c;在输入信号中主要包括开关信号、模拟信号、CAN信号、PWM信号等&#xff0c;其中CAN通讯由于通讯质量高&#xff0c;传输数据量大&#xff0c;采用总线通讯方式节省大量线束&#xff0c;在汽车上尤其是电动汽车上大量应用&#xff0c;当然&#xf…

数图亮相第三届中国区域零售创新峰会:共绘零售新蓝图,携手迈向新征程

8月31日&#xff0c;备受瞩目的第三届中国区域零售创新峰会在历史悠久的湖北襄阳圆满落下帷幕。在这场零售行业的盛会上&#xff0c;数图信息科技作为重要参会企业&#xff0c;积极参与其中&#xff0c;与众多行业精英共聚一堂&#xff0c;共同擘画零售业的宏伟蓝图。以下是本次…

C/C++ 中的算术运算及其陷阱(详解,举例分析)

在C/C编程中&#xff0c;算术运算是非常基础且常用的操作。然而&#xff0c;这些看似简单的运算背后却隐藏着一些潜在的陷阱&#xff0c;如果不加以注意&#xff0c;可能会导致程序出现难以预料的错误。本文将探讨C/C中常见的算术运算及其潜在的陷阱&#xff0c;并通过实例进行…

告别格式不兼容烦恼!ape转换mp3,分享3个简单方法

各位读者们&#xff0c;你们是否有过这种体验&#xff1a;满怀期待地在网上下载一首好听的歌曲&#xff0c;结果怎么点击手机都播放不了&#xff0c;定睛一看&#xff0c;弹窗显示“无法播放该音频文件”。这是为什么呢&#xff1f;原来那首歌的音频格式是ape&#xff0c;不被手…

iOS——关联对象学习补充

分类 在分类中添加属性会生成对应的成员变量&#xff0c;会生成对应的setter和getter方法的声明&#xff0c;但是不会生成setter和getter方法的实现。分类中的可以写property&#xff0c;会编译通过&#xff0c;但是引用变量会报错。分类中可以/只能访问原有类中.h中的属性。如…

如何选择合适的变压吸附制氧设备

在选择合适的变压吸附(Pressure Swing Adsorption, PSA)制氧设备时&#xff0c;需要综合考虑多个因素以确保设备能够高效、稳定地运行&#xff0c;满足特定应用场景的需求。以下是一些关键步骤和考虑因素&#xff0c;帮助您做出明智的决策。 1. 明确应用需求 明确您的制氧需求至…

visual studio 2022更新以后,之前的有些工程编译出错,升级到Visual studio Enterprise 2022 Preview解决

系列文章目录 文章目录 系列文章目录前言一、解决方法 前言 今天遇到一个问题&#xff1a;visual studio 2022升级成预览版以后&#xff0c;之前的有些工程编译出错。首先代码、项目设置都没有改变&#xff0c;只是更新了visual studio 2022。 在编译工程时&#xff0c;编译器…

Team Render 上的 Redshift 照明与我的编辑机器上的不同(如何缓存 Redshift GI)

有时&#xff0c;您的灯光在另一台机器&#xff08;例如属于 Team Render 农场的机器&#xff09;上看起来会与在主/编辑机器上看起来不同。这是因为&#xff0c;即使使用相似或相同的硬件&#xff0c;一台机器计算全局照明的方式与另一台机器也会有所不同。 这可能会导致光线…

Docker 部署 Kibana (图文并茂超详细)

部署 Kibana ( Docker ) [Step 1] : 拉取 Kibana 镜像 docker pull kibana:7.14.0[Step 2] : 创建目录 ➡️ 启动容器 ➡️ 拷贝文件 ➡️ 授权文件 ➡️ 删除容器 # 创建目录 mkdir -p /data/kibana/{conf,plugins}# 启动容器 docker run --name kibana --restartalways \…

科普神文,一次性讲透AI大模型的核心概念

令牌&#xff0c;向量&#xff0c;嵌入&#xff0c;注意力&#xff0c;这些AI大模型名词是否一直让你感觉熟悉又陌生&#xff0c;如果答案肯定的话&#xff0c;那么朋友&#xff0c;今天这篇科普神文不容错过。我将结合大量示例及可视化的图形手段&#xff0c;为你由浅入深一次…

Centos Stream9系统安装及网络配置详解

1.镜像下载 如未拥有系统镜像文件的伙伴可通过前往下面的连接进行下载&#xff0c;下载完成后需将其刻录至U盘中。 PS&#xff1a;该U盘应为空盘&#xff0c;刻录文件会导该盘格式化&#xff0c;下载文件选择dvd1.iso完整包&#xff0c;适用于本地安装。 下载地址&#xff1…

恋爱相亲交友系统源码原生源码可二次开发APP 小程序 H5,web全适配

直播互动&#xff1a;平台设有专门的直播间&#xff0c;允许房间主人与其他异性用户通过视频连线的方式进行一对一互动。语音视频交流&#xff1a;异性用户可以发起语音或视频通话&#xff0c;以增进了解和交流。群组聊天&#xff1a;用户能够创建群聊&#xff0c;邀请自己关注…

【云计算】什么是云计算服务|为什么出现了云计算|云计算的服务模式

文章目录 什么是云计算服务本地部署VS云计算SaaS PaaS IaaS公有云、私有云、混合云为什么优先发展云计算服务的厂商是亚马逊、阿里巴巴等公司 什么是云计算服务 根据不同的目标用户&#xff0c;云计算服务&#xff08;Cloud Computing Services&#xff09;分为两种&#xff1…

探索动销方案创新路径,开启企业增长新引擎

在当今竞争激烈的市场中&#xff0c;动销方案的重要性不言而喻。然而&#xff0c;传统动销手段已难以应对多变的市场环境&#xff0c;企业急需探索创新路径。 当前动销方案面临哪些挑战呢&#xff1f; 首先&#xff0c;消费者需求越发多样化&#xff0c;他们追求个性化和多元化…

如何修复软件中的BUG

笔者上一篇博文《如何开发出一款优秀的软件》主要讲了如何开发一款优秀的软件及相应的必要条件。但对一个已上线&#xff0c;已经成型的产品&#xff0c;该如何解决存在的bug呢&#xff1f;这是本文要阐述的内容。 在这里&#xff0c;首先说一下bug的种类及bug严重程度分类&…

QT: Unable to create a debugging engine.

1.问题场景&#xff1a; 第一次安装QT&#xff0c;没有配置debug功能 打开控制面板》程序》找到Kit 重启电脑即可 2.问题场景&#xff1a; qt原本一直好好的&#xff0c;突然有天打开运行调试版本&#xff0c;提示Unable to create a debugging engine.错误。这个是指无法创…

【计算机网络】TCP连接如何确保传输的可靠性

一、确保可靠传输的机制 TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、提供可靠交付的、面向字节流的、支持全双工的传输层通信协议 1、序列号 seq TCP头部中的序号&#xff0c;占32位&#xff08;4字节&#xff09;&#xff1b; 发送方给报文段分配一个序列号&a…

如何锻炼自己深度思考的能力?4个方法让你快速看清事物的本质!

我们每天都会接触到海量的信息&#xff0c;但真正的智慧并不在于掌握多少信息&#xff0c;而在于如何从中提炼出有价值的知识&#xff0c;并对其进行深刻的理解与运用。 本周想和大家探讨一下深度思考的重要性&#xff0c;同时分享一些实用的方法和技巧&#xff0c;希望能帮你…

STM32(一)简介

一、stm32简介 1.外设接口 通过程序配置外设来完成功能 2.系统结构 3.引脚定义 4.启动配置 5.最小系统电路