I have a series of events, each event contains a timestamp, device, serialnumber, and measurement.
class Event {
private String device;
private String description;
private String serialnumber;
private Measurement measurement;
}
class Measurement {
private LocalDateTime timestamp;
private int value;
}
I have a stream of these events, and I would like to aggregate them into a simpler structure, dropping the serial number, then group them by device, and then sorting the measurements by timestamp and value.
{device: "device_1", description: "first device", serialnumber: "1", measurement: { timestamp: 2022-04-23T18:20:22Z, value: 180}}
{device: "device_2", description: "second device", serialnumber: "2", measurement: { timestamp: 2022-04-23T18:20:28Z, value: 120}}
{device: "device_2", description: "second device", serialnumber: "2", measurement: { timestamp: 2022-04-23T18:20:20Z, value: 160}}
{device: "device_1", description: "first device", serialnumber: "1", measurement: { timestamp: 2022-04-23T18:20:22Z, value: 170}}
[
{
device: "device_1",
description: "first device",
measurements: [
{ timestamp: 2022-04-23T18:20:22Z, value: 170},
{ timestamp: 2022-04-23T18:20:22Z, value: 180}
]
},
{
device: "device_2",
description: "second device",
measurements: [
{ timestamp: 2022-04-23T18:20:20Z, value: 160},
{ timestamp: 2022-04-23T18:20:28Z, value: 120}
]
}
]
I've managed to get the required format by creating a "builder" class in which you can insert events that get then processed and added to the data members in the correct format/order.
However, I think it would be nicer to somehow achieve this on the fly without another extra class, but using the groupingBy
and toMap
(and other?) stream methods.
You could group your Event
objects by their device
, and then collect the Measurement
in a TreeSet
to sort them by their timestamp
and value
.
Map<String, TreeSet<Measurement>> map = dataSource.stream()
.collect(Collectors.groupingBy(
Event::getDevice,
Collectors.mapping(Event::getMeasurement,
Collectors.toCollection(TreeSet::new)
)
)
);
In this scenario, you need to either implement the Comparable
interface on the Measurement
class and provide a definition for compareTo
, or pass a Comparator
to the TreeSet
with the desired sorting logic. In both cases, remember to provide a proper definition so that this is consistent with equals. According to the Comparable
interface:
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.
@Data
@AllArgsConstructor
class Event {
private String device;
private String description;
private String serialnumber;
private Measurement measurement;
}
@Data
@AllArgsConstructor
class Measurement implements Comparable<Measurement> {
private LocalDateTime timestamp;
private int value;
//Redefinition of Measurement's natural ordering by its timestamp and value
@Override
public int compareTo(Measurement m) {
Comparator<Measurement> cmp = Comparator.comparing(Measurement::getTimestamp)
.thenComparing(Measurement::getValue);
return cmp.compare(this, m);
}
}
Here is a demo at OneCompiler