Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 List<T> into Map<K, V>

I want to convert List of Objects to Map, where Map's key and value located as attributes inside Object in List.

Here Java 7 snippet of such convertation:

private Map<String, Child> getChildren(List<Family> families  ) {
        Map<String, Child> convertedMap = new HashMap<String, Child>();

        for (Family family : families) {
            convertedMap.put(family.getId(), family.getParent().getChild());
        }
        return convertedMap;
    }
like image 577
Shurok Avatar asked Sep 25 '14 22:09

Shurok


People also ask

Can we convert list to map in Java?

With Java 8, you can convert a List to Map in one line using the stream() and Collectors. toMap() utility methods. The Collectors. toMap() method collects a stream as a Map and uses its arguments to decide what key/value to use.

How can I turn a list of lists into a list in Java 8?

Here is the simple, concise code to perform the task. // listOfLists is a List<List<Object>>. List<Object> result = new ArrayList<>(); listOfLists. forEach(result::addAll);

What is the use of MAP in Java 8?

Java 8 Stream's map method is intermediate operation and consumes single element forom input Stream and produces single element to output Stream. It simply used to convert Stream of one type to another.


2 Answers

It should be something similar to...

Map<String, Child> m = families.stream()
    .collect(Collectors.toMap(Family::getId, f -> f.getParent().getChild()));
like image 56
Jason Avatar answered Oct 20 '22 11:10

Jason


Jason gave a decent answer (+1) but I should point out that it has different semantics from the OP's Java 7 code. The issue concerns the behavior if two family instances in the input list have duplicate IDs. Maybe they're guaranteed unique, in which case there is no difference. If there are duplicates, though, with the OP's original code, a Family later in the list will overwrite the map entry for a Family earlier in the list that has the same ID.

With Jason's code (shown below, slightly modified):

Map<String, Child> getChildren(List<Family> families) {
    return families.stream()
        .collect(Collectors.toMap(Family::getId, f -> f.getParent().getChild()));
}

the Collectors.toMap operation will throw IllegalStateException if there are any duplicate keys. This is somewhat unpleasant, but at least it notifies you that there are duplicates instead of potentially losing data silently. The rule for Collectors.toMap(keyMapper, valueMapper) is that you need to be sure that the key mapper function returns a unique key for every element of the stream.

What you need to do about this -- if anything -- depends on the problem domain. One possibility is to use the three-arg version: Collectors.toMap(keyMapper, valueMapper, mergeFunction). This specifies an extra function that gets called in the case of duplicates. If you want to have later entries overwrite earlier ones (matching the original Java 7 code), you'd do this:

Map<String, Child> getChildren(List<Family> families) {
    return families.stream()
        .collect(Collectors.toMap(Family::getId, f -> f.getParent().getChild(),
                                                 (child1, child2) -> child2));
}

An alternative would be to build up a list of children for each family instead of having just one child. You could write a more complicated merging function that created a list for the first child and appended to this list for the second and subsequent children. This is so common that there is a special groupingBy collector that does this automatically. By itself this would produce a list of families grouped by ID. We don't want a list of families but instead we want a list of children, so we add a downstream mapping operation to map from family to child, and then collect the children into a list. The code would look like this:

Map<String, List<Child>> getChildren(List<Family> families) {
    return families.stream()
        .collect(Collectors.groupingBy(Family::getId,
                    Collectors.mapping(f -> f.getParent().getChild(),
                        Collectors.toList())));
}

Note that the return type has changed from Map<String, Child> to Map<String, List<Child>>.

like image 9
Stuart Marks Avatar answered Oct 20 '22 11:10

Stuart Marks