Map.replaceAll when changing value types

Paul Sandoz paul.sandoz at oracle.com
Tue Apr 22 14:29:10 UTC 2014


Hi Frank,

On Apr 22, 2014, at 12:10 PM, Frank van Heeswijk <fvheeswijk at outlook.com> wrote:
> 
> Hello,
> 
> First of all, this is the first time I am sending a mail to any java mailing list,

Welcome!


> so please correct me if I did something wrong.
> 
> I am a regular visitor and answered on stackoverflow ( http://stackoverflow.com/users/2057294/skiwi ) and noticed that there are quite a large amount of questions now already asking for a way to easily replace values in maps when their type fundamentally changes.
> I however also have a few concerns while doing this, which will be listed below.
> 
> Take first as example the following code, it may not be the best example, but it should show the intention:
> 
> Map<String, Integer> map = new HashMap<>();
> map.put("test", 5);
> Map<String, String> mapped = map.entrySet().stream()
>        .collect(Collectors.toMap(Map.Entry::getKey, entry -> String.valueOf(entry.getValue())));
> 
> The intention here was to map the integer values to their string counterparts in a single map, functionally this is equivalent to the following:
> 
> Map<String, Integer> map = new HashMap<>();
> map.put("test", 5);
> map.replaceAll((k, v) -> v + 1);
> 
> One would expect the first case to also be a one-liner, possibly somewhat like:
> 
> Map<String, String> mapped = map.replaceAllSpecial((k, v) -> String.valueOf(v));
> 
> Based on this I have the following concerns:
> 1. How would the method exactly need to look like? Should it also have the option to take a Function<V_IN, V_OUT> valueMapper as argument?
> 2. How could this be optimized? Surely we do not *really* want to take all map entries out of one map and insert the modified value in a new map, I was thinking that an in-place modification would be the best, but then you have the issue where the type changes.
> 

Such in place modification is a definitely wrong, you are going to pollute your map so any previous refs to Map<String, Integer> are going to break with a ClassCastException when values are accessed.

To be safe you would need to consistently use Map<String, Object> in such cases. (We don't have union types so you could declare <String | Integer> if you knew up front the types.)

Unfortunately stream-based manipulating maps is more difficult than manipulating lists, as you have noted. One possible solution is to create some more map-oriented collectors, such as:

    public static <T, K, U>
    Collector<Map.Entry<K, T>, ?, Map<K,U>> toMapValue(Function<? super T, ? extends U> valueMapper) {
        return Collectors.toMap(Map.Entry::getKey, e -> valueMapper.apply(e.getValue()));
    }

        Map<String, Integer> a = new HashMap<>();
        ...
        Map<String, String> b =  a.entrySet().stream().collect(toMapValue(v -> v.toString()));

That at least reduces some of the boilerplate and is easier for developers to initially grok.


Generally stream-based operations on maps could be improved with a BiStream<K, V> so you could do:

  map.stream().mapValue(v -> v.toString()).collect(toMap());

We prototyped that (i have such an internal prototype hacked into the latest API) but decided we were not ready to commit to such an API in Java 8 (a number of cases where BiStream may have been useful were subsumed by Collectors.groupingBy), but it still might have value in a future release.

We could add some common basic functionality to the default methods on Map e.g.:

  default Map<K, U> transformValues(Function<? super V, ? extends U> valueMapper) { ... }
  default Map<V, K> swap() throws IllegalStateException; // throws for dup keys
  etc. 

but there is always a danger of adding too much, a high bar would have to set set for that IMHO, a BiStream view of a Map is more satisfying in this respect and aligns with a Stream view of a Collection.

Hth,
Paul.

> Lastly, I hope that we can agree on that for such a trivial usecase, the usage of streams is possibly too complicated and furthermore point (2) is very important.
> 
> As some more reference, here are some of the stackoverflow questions that ask for it:
> - http://stackoverflow.com/questions/23213891/how-to-map-values-in-a-map-in-java-8
> - http://stackoverflow.com/questions/22840170/in-java-8-functional-style-how-can-i-map-the-values-over-a-java-util-map
> 
> Regards,
> 
> Frank van Heeswijk
> 		 	   		   		 	   		  




More information about the core-libs-dev mailing list