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
Lambda expressions can be stored in variables if the variable's type is an interface which has only one method. The lambda expression should have the same number of parameters and the same return type as that method. Java has many of these kinds of interfaces built in, such as the Consumer interface (found in the java.util package) used by lists.
In this article Lambda Expression with Collections are discussed with examples of sorting different collections like ArrayList, TreeSet, TreeMap, etc. We can use Comparator interface to sort, It only contain one abstract method: – compare (). An interface that only contain only a single abstract method then it is called as Functional Interface.
But, as the javadoc indicates, collect () expects either 3 functional interface instances as argument, or a Collector. So you can't just pass a single lambda expression to collect ().
When there is Functional Interface concept used, then we can use Lambda Expression at its place. Using lambda expression in place of comparator object for defining our own sorting in collections.
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With