Java 8 Stream获取最新且去重的事件实现方案问询
Alright, let's tackle this problem: we need to process a collection of Event objects, and for every combination of product ID, event type, and calendar day, we only keep the most recent event (even if there are dozens of the same event type for the same product in a single day).
Step 1: Polish the Event Class
First, let's complete the hashCode method (your original code was cut off) and add necessary getters—we'll need these to access fields in the stream:
import java.util.Date; import java.util.Objects; public class Event { private Date eventDate; private EventType eventType; private Long productId; // Constructor for easy object creation public Event(Date eventDate, EventType eventType, Long productId) { this.eventDate = eventDate; this.eventType = eventType; this.productId = productId; } // Getters to access private fields in stream operations public Date getEventDate() { return eventDate; } public EventType getEventType() { return eventType; } public Long getProductId() { return productId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Event that = (Event) o; return Objects.equals(eventDate, that.eventDate) && eventType == that.eventType && Objects.equals(productId, that.productId); } @Override public int hashCode() { return Objects.hash(eventDate, eventType, productId); } } // Assume EventType is an enum (adjust values to match your use case) enum EventType { KICK, PASS, CATCH }
Step 2: Stream Implementation with Custom Grouping
The core logic is to group events by product ID + event type + calendar day, then pull the latest event from each group. Since Java 8 doesn't have a built-in triple type, we'll make a custom grouping key class for better readability:
import java.time.LocalDate; import java.time.ZoneId; import java.util.Collection; import java.util.Comparator; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; public class EventProcessor { // Custom key to group events by product, type, and calendar day private static class EventGroupKey { private final Long productId; private final EventType eventType; private final LocalDate eventDay; public EventGroupKey(Long productId, EventType eventType, LocalDate eventDay) { this.productId = productId; this.eventType = eventType; this.eventDay = eventDay; } // Required for proper grouping: override equals and hashCode @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EventGroupKey that = (EventGroupKey) o; return Objects.equals(productId, that.productId) && eventType == that.eventType && Objects.equals(eventDay, that.eventDay); } @Override public int hashCode() { return Objects.hash(productId, eventType, eventDay); } } public Collection<Event> getLatestUniqueEvents(Collection<Event> events) { return events.stream() // Filter out invalid events to avoid null pointer exceptions .filter(event -> Objects.nonNull(event.getEventDate()) && Objects.nonNull(event.getProductId()) && Objects.nonNull(event.getEventType())) // Group events by our custom key (product + type + day) .collect(Collectors.groupingBy( event -> { // Convert Date to LocalDate to group by calendar day (ignore time) LocalDate eventDay = event.getEventDate().toInstant() .atZone(ZoneId.systemDefault()) // Use ZoneId.of("UTC") for global consistency .toLocalDate(); return new EventGroupKey(event.getProductId(), event.getEventType(), eventDay); }, // For each group, grab the event with the latest timestamp Collectors.maxBy(Comparator.comparing(Event::getEventDate)) )) // Extract the latest event from each group (handle Optional wrapper) .values().stream() .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } }
Key Details to Note
- Filtering: We first remove events with null critical fields to prevent crashes during stream processing.
- Grouping Logic: The
EventGroupKeyensures that all events for the same product, same event type, and same day are clustered together. ConvertingDatetoLocalDatelets us ignore hours/minutes/seconds and group strictly by calendar day. - Fetching the Latest Event:
Collectors.maxByuses a comparator based oneventDateto pick the most recent event in each group. SincemaxByreturns anOptional, we filter out empty values and extract the actual event. - Timezone Adjustment: If your app uses UTC time instead of the system default, replace
ZoneId.systemDefault()withZoneId.of("UTC")to ensure consistent day grouping across different servers.
内容的提问来源于stack exchange,提问作者michealAtmi

