Search code examples
javacollections

How to use merge function with collections as a map value in Java


I am trying to understand better how to work with collections and functions without streams and I have the next code where I am trying to add a new value to an existing key in a Map where value is a Collection.

But I am stuck with what exactly parameters I need to pass to the merge function in addNewPhoneNumbers() method. So I need some help with this particular code piece and maybe some explanation how it should work with merge or compute functions.

import java.util.*;

class PhoneBook {
    private final Map<String, Collection<PhoneNumber>> nameToPhoneNumbersMap = new HashMap<>();

    public void addNewPhoneNumbers(String name, Collection<PhoneNumber> numbers) {
        if (!nameToPhoneNumbersMap.containsKey(name)) {
            nameToPhoneNumbersMap.put(name, numbers);
        } else{
            System.out.println("name is already in the map, adding new phone...");
            nameToPhoneNumbersMap.merge(name, ??? , ???);
        }
    }

    public void printPhoneBook() {
        nameToPhoneNumbersMap.forEach((k, v) -> {
            System.out.println(k);
            v.forEach(vv -> System.out.println(vv.getType() + ": " + vv.getNumber()));
        });
        System.out.println("------");
    }
}

enum PhoneNumberType {
    MOBILE, HOME, WORK,
}

class PhoneNumber {

    private PhoneNumberType type;
    private String number;

    public PhoneNumber(PhoneNumberType type, String number) {
        this.type = type;
        this.number = number;
    }

    public PhoneNumberType getType() {
        return type;
    }

    public String getNumber() {
        return number;
    }


    public static void main(String[] args) {
        PhoneBook phoneBook = new PhoneBook();

        List<PhoneNumber> saraPhoneNumbers = new ArrayList<>();
        List<PhoneNumber> johnPhoneNumbers = new ArrayList<>();

        saraPhoneNumbers.add(new PhoneNumber(PhoneNumberType.HOME, "1234567"));
        johnPhoneNumbers.add(new PhoneNumber(PhoneNumberType.WORK, "8910"));

        phoneBook.addNewPhoneNumbers("Sara", saraPhoneNumbers);
        phoneBook.printPhoneBook();

        phoneBook.addNewPhoneNumbers("John", johnPhoneNumbers);
        phoneBook.printPhoneBook();

        //add a new name with a new phone number
        phoneBook.addNewPhoneNumbers("Tom", List.of(new PhoneNumber(PhoneNumberType.WORK, "11121314")));
        phoneBook.printPhoneBook();

        //add a new phone with a new phone type for an existing name
        phoneBook.addNewPhoneNumbers("Sara", List.of(new PhoneNumber(PhoneNumberType.MOBILE, "15161718")));
        phoneBook.printPhoneBook();
    }

}

For now the last sout result doesn't contain Sara's mobile phone number, but it should be like this:

name is already in the map, adding new phone...

Tom

WORK: 11121314

John

WORK: 8910

Sara

HOME: 1234567

MOBILE: 15161718


Solution

  • Use computeIfAbsent to add a new collection if key is absent or add values if it exists

    public void addNewPhoneNumbers(String name, Collection<PhoneNumber> numbers) {
        if (nameToPhoneNumbersMap.containsKey(name)) {
            System.out.println(name + " is already in the map, adding new phone...");
        }
    
        nameToPhoneNumbersMap.computeIfAbsent(name, k -> new ArrayList<>()).addAll(numbers);
    }
    

    You can achieve the same with merge, but need to take care of the fact that Collection.addAll returns a boolean. That is : in case the key already exists you need to add the new numbers to the old list and return the old list or the other way round.

    public void addNewPhoneNumbers(String name, Collection<PhoneNumber> numbers) {
        if (nameToPhoneNumbersMap.containsKey(name)) {
            System.out.println(name + " is already in the map, adding new phone...");
        }
        nameToPhoneNumbersMap.merge(name, numbers, (oldValue,newValue) -> {
            oldValue.addAll(newValue);
            return oldValue;
        });
    }