Java 流:使用 groupingBy 而不是列表时如何映射到单个项目
Java stream: How to map to a single item when using groupingBy instead of to list
收到订单 class 喜欢:
@ToString
@AllArgsConstructor
@Getter
static class Order {
long customerId;
LocalDate orderDate;
}
和订单列表:
List<Order> orderList = List.of(new Order(1, LocalDate.of(2020,Month.APRIL,21)),
new Order(1, LocalDate.of(2021,Month.APRIL,21)),
new Order(1, LocalDate.of(2022,Month.APRIL,21)),
new Order(2, LocalDate.of(2020,Month.APRIL,21)),
new Order(2, LocalDate.of(2021,Month.APRIL,21)),
new Order(3, LocalDate.of(2020,Month.APRIL,21)),
new Order(3, LocalDate.of(2022,Month.APRIL,21)),
new Order(4, LocalDate.of(2020,Month.APRIL,21)));
我需要获取 customerId
的列表,其中最后一个 orderDate
超过 6 个月。所以对于上面的例子 [2,4]
。我的想法是首先按 customerId
分组,第二个映射到最后一个 orderDate
,第三个过滤 6 个月以上的映射。关于如何使用最近的 orderDate
映射到单个订单,我卡在了第二步
第一步
Map<Long, List<Order>> grouped =
orderList.stream()
.collect(Collectors.groupingBy(Order::getCustomerId));
第二步(卡在这里如何更改以上内容以仅获取一项作为值)
Map<Long, Order> grouped =
orderList.stream()
.collect(Collectors.groupingBy(Order::getCustomerId, ???));
甚至更好
Map<Long, LocalDate> grouped =
orderList.stream()
.collect(Collectors.groupingBy(Order::getCustomerId, ???));
我试过使用 Collectors.mapping()
、 Collectors.reducing()
和 Collectors.maxBy()
但是有很多编译错误。
您可以将 Collectors.toMap 与 mergeFunction 一起用于第 2 步:
Map<Long, LocalDate> latestOrderByCustomer =
orderList.stream()
.collect(Collectors.toMap(Order::customerId,
Order::orderDate,
(order1, order2) -> order1.isAfter(order2) ? order1 : order2));
使用 Collectors.toMap
收集器获得按客户 ID 列出的订单的映射。之后,您可以只过滤那些超过 6 个月的订单。
参见下面的实现:
import java.time.LocalDate;
import java.time.chrono.ChronoLocalDate;
import java.util.List;
import java.util.Optional;
import java.util.Collections;
import java.util.Objects;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
public static List<Long> getCustomerIdsOfOrdersOlderThanSixMonths(final List<Order> orderList) {
return Optional.ofNullable(orderList)
.orElse(Collections.emptyList())
.stream()
.filter(o -> Objects.nonNull(o) && Objects.nonNull(o.getOrderDate()))
.collect(Collectors.toMap(
Order::getCustomerId,
Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(Order::getOrderDate))))
.values()
.stream()
.filter(o -> o.getOrderDate()
.plusMonths(6)
.isBefore(ChronoLocalDate.from(LocalDate.now())))
.map(Order::getCustomerId)
.collect(Collectors.toList());
}
}
List<Long> customerIds = getCustomerIdsOfOrdersOlderThanSixMonths(orderList);
// [2, 4]
您可以将 groupingBy()
与下游 maxBy()
收集器一起使用,然后将结果过滤为仅超过六个月的日期:
import java.time.LocalDate;
import java.time.Month;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class Demo {
private record Order(long customerId, LocalDate orderDate) {}
public static void main(String[] args) {
List<Order> orderList =
List.of(new Order(1, LocalDate.of(2020,Month.APRIL,21)),
new Order(1, LocalDate.of(2021,Month.APRIL,21)),
new Order(1, LocalDate.of(2022,Month.APRIL,21)),
new Order(2, LocalDate.of(2020,Month.APRIL,21)),
new Order(2, LocalDate.of(2021,Month.APRIL,21)),
new Order(3, LocalDate.of(2020,Month.APRIL,21)),
new Order(3, LocalDate.of(2022,Month.APRIL,21)),
new Order(4, LocalDate.of(2020,Month.APRIL,21)));
final LocalDate sixMonthsAgo = LocalDate.now().minusMonths(6);
List<Order> mostRecentOrders =
orderList.stream()
.collect(Collectors.groupingBy(Order::customerId,
Collectors.maxBy(Comparator.comparing(Order::orderDate))))
.values().stream()
.filter(opt -> opt.filter(o -> o.orderDate().isBefore(sixMonthsAgo)).isPresent())
.map(Optional::orElseThrow)
.collect(Collectors.toList());
System.out.println(oldOrders);
}
}
产出
[Order[customerId=2, orderDate=2021-04-21], Order[customerId=4, orderDate=2020-04-21]]
首先,您会得到一张客户 ID 地图和(由于 Collectors.maxBy()
的工作方式,包裹在 Optional
中)最近的日期订单。然后过滤掉最近日期在最近六个月内的条目。然后从 Optional
中提取剩余的订单,并将它们 return 放在 List
中。如果您只需要客户 ID,而不关心 Order
对象的其余部分,请适当修改最终的 map()
和 returned 类型。
这是执行此操作的另一种方法。使用 Collectors.partioningBy
分隔日期。设置几个变量以帮助在分区和打印过程中保持正常。
static boolean WITHIN_LAST_SIX_MONTHS = false;
static boolean BEFORE_SIX_MONTHS_AGO = true;
- 根据订单是在 6 个月前发布 (true) 还是在过去 6 个月内发布 (false) 来划分订单。
- 然后将订单映射到
CustomerId
和 return 作为 Set
(无需重复)
- 就是这样。
Map<Boolean, Set<Long>> result = orderList.stream()
.collect(Collectors.partitioningBy(
order -> order.getOrderDate()
.isBefore(LocalDate.now()
.minusMonths(6)),
Collectors.mapping(
Order::getCustomerId,
Collectors.toSet())));
System.out.println("BEFORE_SIX_MONTHS_AGO="+result.get(BEFORE_SIX_MONTHS_AGO));
System.out.println("WITHIN_LAST_SIX_MONTHS="+result.get(WITHIN_LAST_SIX_MONTHS));
打印
BEFORE_SIX_MONTHS_AGO=[1, 2, 3, 4]
WITHIN_LAST_SIX_MONTHS=[1, 3]
现在只需从六个月前发生的订单中删除最近订单的 ID。
result.get(BEFORE_SIX_MONTHS_AGO).removeAll(result.get(WITHIN_LAST_SIX_MONTHS));
System.out.println(result.get(BEFORE_SIX_MONTHS_AGO));
打印
[2, 4]
请注意,对于分区收集器,可以使用 collectingAndThen
直接从 stream
中 return 编辑最终集。整理器看起来像这样。
thismap -> {
Set<Long> retSet = new HashSet<>(thismap.get(BEFORE_SIX_MONTHS_AGO));
retSet.removeAll(thismap.get(WITHIN_LAST_SIX_MONTHS));
return retSet;
}
但是我觉得这很忙,实际上使事情复杂化。
收到订单 class 喜欢:
@ToString
@AllArgsConstructor
@Getter
static class Order {
long customerId;
LocalDate orderDate;
}
和订单列表:
List<Order> orderList = List.of(new Order(1, LocalDate.of(2020,Month.APRIL,21)),
new Order(1, LocalDate.of(2021,Month.APRIL,21)),
new Order(1, LocalDate.of(2022,Month.APRIL,21)),
new Order(2, LocalDate.of(2020,Month.APRIL,21)),
new Order(2, LocalDate.of(2021,Month.APRIL,21)),
new Order(3, LocalDate.of(2020,Month.APRIL,21)),
new Order(3, LocalDate.of(2022,Month.APRIL,21)),
new Order(4, LocalDate.of(2020,Month.APRIL,21)));
我需要获取 customerId
的列表,其中最后一个 orderDate
超过 6 个月。所以对于上面的例子 [2,4]
。我的想法是首先按 customerId
分组,第二个映射到最后一个 orderDate
,第三个过滤 6 个月以上的映射。关于如何使用最近的 orderDate
第一步
Map<Long, List<Order>> grouped =
orderList.stream()
.collect(Collectors.groupingBy(Order::getCustomerId));
第二步(卡在这里如何更改以上内容以仅获取一项作为值)
Map<Long, Order> grouped =
orderList.stream()
.collect(Collectors.groupingBy(Order::getCustomerId, ???));
甚至更好
Map<Long, LocalDate> grouped =
orderList.stream()
.collect(Collectors.groupingBy(Order::getCustomerId, ???));
我试过使用 Collectors.mapping()
、 Collectors.reducing()
和 Collectors.maxBy()
但是有很多编译错误。
您可以将 Collectors.toMap 与 mergeFunction 一起用于第 2 步:
Map<Long, LocalDate> latestOrderByCustomer =
orderList.stream()
.collect(Collectors.toMap(Order::customerId,
Order::orderDate,
(order1, order2) -> order1.isAfter(order2) ? order1 : order2));
使用 Collectors.toMap
收集器获得按客户 ID 列出的订单的映射。之后,您可以只过滤那些超过 6 个月的订单。
参见下面的实现:
import java.time.LocalDate;
import java.time.chrono.ChronoLocalDate;
import java.util.List;
import java.util.Optional;
import java.util.Collections;
import java.util.Objects;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
public static List<Long> getCustomerIdsOfOrdersOlderThanSixMonths(final List<Order> orderList) {
return Optional.ofNullable(orderList)
.orElse(Collections.emptyList())
.stream()
.filter(o -> Objects.nonNull(o) && Objects.nonNull(o.getOrderDate()))
.collect(Collectors.toMap(
Order::getCustomerId,
Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(Order::getOrderDate))))
.values()
.stream()
.filter(o -> o.getOrderDate()
.plusMonths(6)
.isBefore(ChronoLocalDate.from(LocalDate.now())))
.map(Order::getCustomerId)
.collect(Collectors.toList());
}
}
List<Long> customerIds = getCustomerIdsOfOrdersOlderThanSixMonths(orderList);
// [2, 4]
您可以将 groupingBy()
与下游 maxBy()
收集器一起使用,然后将结果过滤为仅超过六个月的日期:
import java.time.LocalDate;
import java.time.Month;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class Demo {
private record Order(long customerId, LocalDate orderDate) {}
public static void main(String[] args) {
List<Order> orderList =
List.of(new Order(1, LocalDate.of(2020,Month.APRIL,21)),
new Order(1, LocalDate.of(2021,Month.APRIL,21)),
new Order(1, LocalDate.of(2022,Month.APRIL,21)),
new Order(2, LocalDate.of(2020,Month.APRIL,21)),
new Order(2, LocalDate.of(2021,Month.APRIL,21)),
new Order(3, LocalDate.of(2020,Month.APRIL,21)),
new Order(3, LocalDate.of(2022,Month.APRIL,21)),
new Order(4, LocalDate.of(2020,Month.APRIL,21)));
final LocalDate sixMonthsAgo = LocalDate.now().minusMonths(6);
List<Order> mostRecentOrders =
orderList.stream()
.collect(Collectors.groupingBy(Order::customerId,
Collectors.maxBy(Comparator.comparing(Order::orderDate))))
.values().stream()
.filter(opt -> opt.filter(o -> o.orderDate().isBefore(sixMonthsAgo)).isPresent())
.map(Optional::orElseThrow)
.collect(Collectors.toList());
System.out.println(oldOrders);
}
}
产出
[Order[customerId=2, orderDate=2021-04-21], Order[customerId=4, orderDate=2020-04-21]]
首先,您会得到一张客户 ID 地图和(由于 Collectors.maxBy()
的工作方式,包裹在 Optional
中)最近的日期订单。然后过滤掉最近日期在最近六个月内的条目。然后从 Optional
中提取剩余的订单,并将它们 return 放在 List
中。如果您只需要客户 ID,而不关心 Order
对象的其余部分,请适当修改最终的 map()
和 returned 类型。
这是执行此操作的另一种方法。使用 Collectors.partioningBy
分隔日期。设置几个变量以帮助在分区和打印过程中保持正常。
static boolean WITHIN_LAST_SIX_MONTHS = false;
static boolean BEFORE_SIX_MONTHS_AGO = true;
- 根据订单是在 6 个月前发布 (true) 还是在过去 6 个月内发布 (false) 来划分订单。
- 然后将订单映射到
CustomerId
和 return 作为Set
(无需重复) - 就是这样。
Map<Boolean, Set<Long>> result = orderList.stream()
.collect(Collectors.partitioningBy(
order -> order.getOrderDate()
.isBefore(LocalDate.now()
.minusMonths(6)),
Collectors.mapping(
Order::getCustomerId,
Collectors.toSet())));
System.out.println("BEFORE_SIX_MONTHS_AGO="+result.get(BEFORE_SIX_MONTHS_AGO));
System.out.println("WITHIN_LAST_SIX_MONTHS="+result.get(WITHIN_LAST_SIX_MONTHS));
打印
BEFORE_SIX_MONTHS_AGO=[1, 2, 3, 4]
WITHIN_LAST_SIX_MONTHS=[1, 3]
现在只需从六个月前发生的订单中删除最近订单的 ID。
result.get(BEFORE_SIX_MONTHS_AGO).removeAll(result.get(WITHIN_LAST_SIX_MONTHS));
System.out.println(result.get(BEFORE_SIX_MONTHS_AGO));
打印
[2, 4]
请注意,对于分区收集器,可以使用 collectingAndThen
直接从 stream
中 return 编辑最终集。整理器看起来像这样。
thismap -> {
Set<Long> retSet = new HashSet<>(thismap.get(BEFORE_SIX_MONTHS_AGO));
retSet.removeAll(thismap.get(WITHIN_LAST_SIX_MONTHS));
return retSet;
}
但是我觉得这很忙,实际上使事情复杂化。