Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Java 8 streams to transform a Map with nulls

I am dealing with a Map<String,String> that has null entries in the key and/or value:

Map<String, String> headers = new HashMap<>();
headers.put("SomE", "GreETing");
headers.put("HELLO", null);
headers.put(null, "WOrLd");

headers.keySet().stream().forEach(k -> System.out.println(k + " => " + copy.get(k)));

I get the following output:

SomE => GreETing
HELLO => null
null => WOrLd

I need to transform the map, so all the non-null values are converted to lowercase, like so:

some => greeting
hello => null
null => world

I am trying to use Java 8 streams API, but the following code is throwing NullPointerException:

Map<String,String> copy
    = headers.entrySet()
             .stream()
             .collect(
                 Collectors.toMap(
                     it -> it.getKey() != null ? it.getKey().toLowerCase() : null,
                     it -> it.getValue() != null ? it.getValue().toLowerCase() : null));

copy.keySet().stream().forEach(k -> System.out.println(k + " => " + copy.get(k)));

If I comment out the last two map entries, the program executes, so there must be an issue with how Collectors.toMap works when keys or values are null. How do I use the streams API to work around this?

like image 814
Web User Avatar asked Mar 02 '17 04:03

Web User


People also ask

How do you handle a null map in Java?

You must try and ensure that your Map doesn't allow null writes such as element2. put("e",null); in reality. This was corrected in JDK itself with Map. of implementation in Java-9 as well.

How do I filter null values in Java 8 stream?

We can use lambda expression str -> str!= null inside stream filter() to filter out null values from a stream.

Can Java map have null values?

Values entered in a map can be null .


2 Answers

The problem is toMap() invokes the underlying Map implementation being built's merge() function which does not allow values to be null

from the javadoc for Map#merge (emphasis mine)

If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function, or removes if the result is null.

So using Collectors.toMap() will not work.

You can do this without stream just fine:

Map<String,String> copy = new HashMap<>();

for(Entry<String, String> entry : headers.entrySet()){
    copy.put(entry.getKey() !=null ? entry.getKey().toLowerCase() : null, 
             entry.getValue() !=null ? entry.getValue().toLowerCase() : null
            );
}
like image 183
dkatzel Avatar answered Oct 10 '22 14:10

dkatzel


Use Collect:

final Function<String, String> fn= str -> str == null ? null : str.toLowerCase();
Map<String, String> copy = headers.entrySet().stream()
   .collect(HashMap::new,
            (m, e) -> m.put(fn.apply(e.getKey()), fn.apply(e.getValue())), 
            Map::putAll);

Or with AbacusUtil

Map<String, String> copy = Stream.of(headers)
   .collect(HashMap::new, 
     (m, e) -> m.put(N.toLowerCase(e.getKey()), N.toLowerCase(e.getValue())));

updated on 2/4, Or:

Map<String, String> copy = EntryStream.of(headers)
   .toMap(entry -> N.toLowerCase(entry.getKey()), entry -> N.toLowerCase(entry.getValue()));
like image 36
user_3380739 Avatar answered Oct 10 '22 14:10

user_3380739