Java 8 Refining Lambda Map<String, List<String>> to String With Map Key Requirement

Viewed 191

The keys of the map may be values 1 - 5 where each key is not required, but if absent must concatenate something that looks like " - [] ".

I've used Java 8 a bit before and I feel like this can be done more efficiently with stream() and collect() but I feel like the external list requirement is throwing me off. The below code works but feels incorrect.

List<String> ids = Arrays.asList("1", "2", "3", "4", "5");

StringBuilder location = new StringBuilder();

ids.forEach(id -> {
    List<String> addresses = map.get(id);
    if (addresses != null) {
        addresses.forEach(addr -> location.append(addr + " - "));
    } else {
        location.append(" [] - ");
    }
});

Generates something that looks like this for a Colorado location:

US - CO - [] - CENTENNIAL -

In answer to Nicholas K an example of map would be

{1=[US], 2=[CO], 5=[METROPOLITAN FOOTBALL STADIUM DISTRICT, REGIONAL TRANSPORTATION DISTRICT]}

and would be expected to receive output

US - CO - [] - [] - METROPOLITAN FOOTBALL STADIUM DISTRICT - REGIONAL TRANSPORTATION DISTRICT
4 Answers

The correct way to handle missing values in a map is computeIfAbsent() which is a new method on Map available since Java 8.

This gives you the below code:

String s = ids.stream()
    .map(id -> map.computeIfAbsent(id, absentKey -> singletonList("[]")))
    .flatMap(Collection::stream)
    .collect(Collectors.joining(" - "));
  • First, let's stream over the keys in the map, which you have stored in the ids list variable.
  • Then, if the value is null, map to a default value ("[]"), else computeIfAbsent() returns the actual value.
  • Since you want to print all the values separated by " - ", let's just flat map the stream of lists and join them by " - ".

s now holds the value (if used with your example map):

"US - CO - [] - [] - METROPOLITAN FOOTBALL STADIUM DISTRICT - REGIONAL TRANSPORTATION DISTRICT"

Notice that there is no trailing " - ". Collectors.joining() makes sure to not append the delimiter before or after the elements.

I am using Stream and Optional to handle null references:

List<String> result = ids.stream()
  .map(index -> format(map.get(index)))
  .collect(Collectors.toList());
System.out.println(result);

And the method format:

  private static String format(List<String> address) {
    return Optional.ofNullable(address)
      .map(a -> a.stream().map(part -> Optional.ofNullable(part).orElse("[]")).collect(Collectors.joining(" - ")))
      .orElse(" [] - ");
  }

Applied on an example:

  public static void main(String[] args) {
    Map<String, List<String>> map = Map.of("1", Arrays.asList("US", "CO", null, "CENTENNIAL"));

    List<String> ids = Arrays.asList("1", "2", "3", "4", "5");

    List<String> result = ids.stream()
      .map(index -> format(map.get(index)))
      .collect(Collectors.toList());
    System.out.println(result);
  }

That prints:

[US - CO - [] - CENTENNIAL,  [] - ,  [] - ,  [] - ,  [] - ]

Simpler approach :

    ids.stream().map(l -> map.get(Integer.parseInt(l))).forEach(i -> {
        if (i != null) {
            location.append(i + " - ");
        } else {
            location.append("[] - ");
        }
    });

Here we loop over each element in the list to see whether it is present as a key in the map. Depending on the value we append the appropriate strings.

Note:

Parsing has been done assuming your map is defined as Map<Integer, String>

Maybe not very clean, but you can try :

Map<String, List<String>> inputMap = Map.of("1", List.of(), "2", List.of("l1", "l2"));
List<String> ids = List.of("1", "2", "3", "4", "5");
StringBuilder location = new StringBuilder();
ids.forEach(id -> {
    if (inputMap.get(id) != null) {
        location.append(inputMap.get(id).stream().map(addr -> addr + " - ").collect(Collectors.joining()));
    } else {
        location.append(" [] - ");
    }
});
Related