I have been searching around different groupBy and stream threads but cannot find the answer to my problem. Basically I have this object:
public class Object {
string name;
string type;
}
And I return a list of these from the database. What I would like to then do is iterate through the list of objects and remove duplicate names and save a list of the second properties under one object, in a new object that looks like this:
public class NewObject {
String name;
List<String> types;
}
CodePudding user response:
You can do it like this.
- use groupingBy to create a map of
name, List of type
- use the entryset of the map to create the new object.
I added the appropriate constructors and getters in the classes.
List<OldObject> list = ...
List<NewObject> newList = list.stream()
.collect(Collectors.groupingBy(OldObject::getName,
Collectors.mapping(OldObject::getType,
Collectors.toList())))
.entrySet().stream()
.map(e -> new NewObject(e.getKey(), e.getValue()))
.toList();
}
class OldObject {
String name;
String type;
public String getName() {
return name;
}
public String getType() {
return type;
}
}
class NewObject {
String name;
List<String> types = new ArrayList<>();
public NewObject(String name, List<String> types) {
this.types = types;
this.name = name;
}
}
For your added enjoyment, I thought I would also offer the following:
- create the map
- conditionally create a new object if the name key is not present
- in either case, add the type to the list instance of the
NewObject
instance in the map which is returned by thecomputeIfAbsent
method.
When finished, just assign the values to your Collection.
Map<String, NewObject> map = new HashMap<>();
for (OldObject ob : list) {
map.computeIfAbsent(ob.getName(),
v -> new NewObject(ob.getName(),
new ArrayList<>()))
.add(ob.getType());
}
Collection<NewObject> newLista = map.values();
Caveats:
- values returns a Collection, not a list so you would need to use that or pass the Collection to a list constructor of some sort (e.g. ArrayList).
- the requires the addition of a add method in the
NewObject
class. - you could also have a getter that returns the type list directly and do.
map.computeIfAbsent(ob.getName(),
v -> new NewObject(ob.getName(),
new ArrayList<>()))
.getTypeList().add(ob.getType());
Check out these additions to the Map interface
CodePudding user response:
I understand you don't want to remove duplicates but actually merge them.
I replaced your Object
class name with OldObject
to avoid confusion with the actual Java Object
class.
Collecting to Map<String, List<String>> and then converting to List<NewObject>
You could write your own Collector
(see later in the answer), but the easiest way of writing what you need would be to use the current groupingBy
and toList
Collector
s and then using the resulting Map
and create your newObject
instance based on it to then again collect to a new List:
oldObjectList.stream().collect(Collectors.groupingBy(OldObject::getName, Collectors.mapping(OldObject::getType, Collectors.toList())))
.entrySet().stream()
// Assuming a NewObject constructor that receives a String name and a List<String> types
.map(e -> new NewObject(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Custom Collectors
If you are interested in learning how to make your own Collector
, I made an example using a custom Collector
as well:
oldObjectList.stream().collect(Collector.of(
// Supplier
() -> new ConcurrentHashMap<String, NewObject>(),
// Accumulator
(map, oldObject) -> {
// Assuming a NewObject constructor that gets a name and creates a new empty List as types.
map.computeIfAbsent(oldObject.getName(), name -> new NewObject(name)).getTypes().add(oldObject.getType());
},
// Combiner
(map1, map2) -> {
map2.forEach((k, v) -> map1.merge(k, v, (v1, v2) -> {
v1.getTypes().addAll(v2.getTypes());
return v1;
}));
return map1;
},
// Finisher
map -> new ArrayList<>(map.values())
));
You can even combine groupingBy
and a custom Collector
as a merge function:
new ArrayList<>(oldObjectList.stream().collect(Collectors.groupingBy(OldObject::getName, Collector.of(
// Supplier
// Assuming a NewObject constructor with no arguments that creates a new empty List as types.
NewObject::new,
// Accumulator
(newObject, oldObject) -> {
newObject.setName(oldObject.getName());
newObject.getTypes().add(oldObject.getType());
},
// Combiner
(newObject1, newObject2) -> {
newObject1.getTypes().addAll(newObject2.getTypes());
return newObject1;
}
))).values());