I've been struggling reading the javadocs to determine how to use lambdas to elegantly combine a list of rows of one type into a grouped-up list of another type.
I've figured out how to use the Collectors.groupingBy
syntax to get the data into a Map<String, List<String>>
but since the results will be used in a variety of later function calls... I'd ideally like to have these reduced to a list of objects which contain the new mapping.
Here are my data types, RowData
is the source... I want to get the data combined into a list of CodesToBrands
:
class RowData {
private String id;
private String name;
public RowData() {
}
public RowData(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class CodeToBrands {
private String code;
private List<String> brands = new ArrayList<>();
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<String> getBrands() {
return brands;
}
public void addBrands(List<String> brands) {
this.brands.addAll(brands);
}
public void addBrand(String brand) {
this.brands.add(brand);
}
}
Here's the test I'm writing to try and figure it out...
@Test
public void testMappingRows() {
List<RowData> rows = new ArrayList<>();
rows.add(new RowData("A", "Foo"));
rows.add(new RowData("B", "Foo"));
rows.add(new RowData("A", "Bar"));
rows.add(new RowData("B", "Zoo"));
rows.add(new RowData("C", "Elf"));
// Groups a list of elements to a Map<String, List<String>>
System.out.println("\nMapping the codes to a list of brands");
Map<String, List<String>> result = rows.stream()
.collect(Collectors.groupingBy(RowData::getId, Collectors.mapping(RowData::getName, Collectors.toList())));
// Show results are grouped nicely
result.entrySet().forEach((entry) -> {
System.out.println("Key: " + entry.getKey());
entry.getValue().forEach((value) -> System.out.println("..Value: " + value));
});
/**Prints:
* Mapping the codes to a list of brands
Key: A
..Value: Foo
..Value: Bar
Key: B
..Value: Foo
..Value: Zoo
Key: C
..Value: Elf*/
// How to get these as a List<CodeToBrand> objects where each CodeToBrand objects to avoid working with a Map<String, List<String>>?
List<CodeToBrands> resultsAsNewType;
}
Can anyone provide any help in trying to get this same overall result in a easier-to-use datatype?
Thanks in advance
You could do it in one pass using Collectors.toMap
:
Collection<CodeToBrands> values = rows.stream()
.collect(Collectors.toMap(
RowData::getId,
rowData -> {
CodeToBrands codeToBrands = new CodeToBrands();
codeToBrands.setCode(rowData.getId());
codeToBrands.addBrand(row.getName());
return codeToBrands;
},
(left, right) -> {
left.addBrands(right.getBrands());
return left;
}))
.values();
Then, if you need a List
instead of a Collection
, simply do:
List<CodeToBrands> result = new ArrayList<>(values);
The code above could be simplified if you had a specific constructor and a merge method in the CodeToBrands
class:
public CodeToBrands(String code, String brand) {
this.code = code;
this.brands.add(brand);
}
public CodeToBrands merge(CodeToBrands another) {
this.brands.addAll(another.getBrands());
return this;
}
Then, simply do:
Collection<CodeToBrands> values = rows.stream()
.collect(Collectors.toMap(
RowData::getId,
rowData -> new CodeToBrands(rowData.getId(), rowData.getName()),
CodeToBrands::merge))
.values();