优质博文:IT-BLOG-CN
大家都知道可以将Collection
类转化成流Stream
进行操作(Map
并不能创建流),代码变得简约流畅。我们先看下流的几个特点:
1、流并不存储元素。这些元素可能存储在底层的集合中,或者是按需生成。
2、流的操作不会修改其数据元素,而是生成一个新的流。
3、流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。
一、创建流
负责新建一个Stream
流,大多数都是基于现有的数组、List
、Set
、Map
等集合创建新的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
下面的代码因对map
和filter
功能的混淆,导致代码执行解决与预期不符,最终出现生产故障。
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()
是否为空的处理,CollectionUtils
和Collectors
被频繁使用,可以进行静态导入以简化代码。
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);
代码优化:上述代码distinct
与count
结合使用时,可以使用Set
与length()
方法实现,但是这里使用count
和distinct
可能从业务上理解更为接近,所以具体需要根据业务场景决定。
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方法
获取一个集合类的结果对象,比如List
、Set
或者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));