How to use Java lambdas to collect elements in a list of a new type?

Cuga :

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

Federico Peralta Schaffner :

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();

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=466057&siteId=1