Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 212
Cuga Avatar asked Mar 20 '18 19:03

Cuga


People also ask

Can lambda expressions be stored in variables in Java?

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.

How to sort different collections using lambda expression with collections?

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.

How many lambda expressions can be passed to collect()?

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 to use lambda expression instead of comparator object?

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.


1 Answers

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();
like image 62
fps Avatar answered Sep 28 '22 07:09

fps