The idea is to
- Stream
Object[] as {String,Long,String}
- Group By
{Object[1],Object[2]}; Collect(Distinct Object[0])
Map<List<Object[0]>,{Object[1],Object[2]}> --> List<MyDto>
For this, I setup two supporting classes, one of which you provided, the other one needed as a Key for the groupingBuy to work:
class MyDTO{
String modelId;
String serviceId;
List<String> vehicleIds;
MyDTO(String modelId, String serviceId, String vehicleId) {
// Need to put in a Modifiable ArrayList so the reduce can happen
this(modelId,serviceId,new ArrayList<>(List.of(vehicleId)));
}
MyDTO(String modelId, String serviceId, List<String> vehicleIds){
this.modelId = modelId;
this.serviceId = serviceId;
this.vehicleIds = vehicleIds;
}
public MyDTOKey getKey() {
return new MyDTOKey(modelId, serviceId);
}
public MyDTO reduce(MyDTO other) {
this.vehicleIds.addAll(other.vehicleIds);
return this;
}
public void dump() {
System.out.println("modelId: "+modelId+"; serviceId: "+serviceId+"; vehicleIds: "+vehicleIds.toString());
}
}
private class MyDTOKey<T1,T2> {
T1 v1;
T2 v2;
public MyDTOKey(T1 v1,T2 v2) {
this.v1 = v1;
this.v2 = v2;
}
@Override
public int hashCode() {
return Objects.hash(v1,v2);
}
/*
* Required for the groupby to work correctly as it
* doesnt automatically on an Object[]
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final MyDTOKey <?, ?> other = (MyDTOKey <?, ?>) obj;
if (!Objects.equals(this.v1, other.v1)) {
return false;
}
return Objects.equals(this.v2, other.v2);
}
}
And with a heads-on approach, I can build something like this:
List<Object []> data = List.of(
new Object[]{"93bf3e92-7a37-4e23-836d-eed5a104341f", "214", "80a7-ce5640b18879"},
new Object[]{"b4066520-e127-44b7-bcc0-1d1187de559c", "214", "80a7-ce5640b18879"},
new Object[]{"b4066520-e127-44b7-bcc0-dddddddddddd", "215", "80a7-ce5640b18879"}
);
public static void main(String [] pars) {
Map<Pair<String,String>,List<String>> map = data
.stream()
.collect(groupingBy(
o->new MyDTOKey((String)o[1],(String)o[2]), // group by last two only
mapping(
o->(String)o[0], // Collapse the vehicle id's to a list
toList() // Using set to take out duplicate vehicle id's
)
));
// Convert the map to a List.
List<MyDTO> dtos = map.entrySet().stream()
.map(e->new MyDTO(e.getKey().v1,e.getKey().v2,e.getValue()))
.collect(toList());
for (MyDTO dto:dtos) {
dto.dump();
}
}
The whole effort of creating a class to represent a Tuple (MyDTOKey) can be avoided by setting it up as a List<String> as done in @Valerij Doblers answer.
But with some minor tweaking already done to the MyDTO class, we can rewrite this also neatly as follows:
public static void main(String [] pars) {
List<MyDTO> data = List.of(
new MyDTO("214", "80a7-ce5640b18879","93bf3e92-7a37-4e23-836d-eed5a104341f"),
new MyDTO("214", "80a7-ce5640b18879","b4066520-e127-44b7-bcc0-1d1187de559c"),
new MyDTO("215", "80a7-ce5640b18879","b4066520-e127-44b7-bcc0-dddddddddddd")
);
List<MyDTO> dtos = data.stream()
.collect(
groupingBy(
MyDTO::getKey,
reducing(MyDTO::reduce)
)
) // Produces a Map<MyDTOKey,MyDTO>
.values().stream() // Streams Optional<MyDTO>
.flatMap(o->o.stream()) // expand to contained MyDTO
.collect(toList())
;
for (MyDTO dto:dtos) {
dto.dump();
}
}
Maybe with a little more effort we can collect directly in a List without going through the Map first.