33

I have a Map<String, String> the String key is nothing but numeric value like "123" etc. I'm getting numeric value because this values are coming from the UI in my JSF component. I don't want to change the contract of UI component.

Now I would like to create a Map<Long, String> based on the above Map, I saw some transform methods in the Maps class but all are focusing on the converting value and not key.

Is there any better way to convert Map<String, String> to Map<Long, String> ?

azro
  • 53,056
  • 7
  • 34
  • 70
Premraj
  • 7,802
  • 8
  • 45
  • 66
  • I don't think there's a built-in feature for this. An although I don't see much use for this, you might want to [file a feature request](http://code.google.com/p/guava-libraries/issues/list). – Joachim Sauer Apr 20 '11 at 15:58

6 Answers6

42

@Vivin's answer is correct, but I think it's useful to explain why Guava doesn't have any method to allow you to transform the keys of a Map (or to transform a Set at all).

All of Guava's methods for transforming and filtering produce lazy results... the function/predicate is only applied when needed as the object is used. They don't create copies. Because of that, though, a transformation can easily break the requirements of a Set.

Let's say, for example, you have a Map<String, String> that contains both "1" and "01" as keys. They are both distinct Strings, and so the Map can legally contain both as keys. If you transform them using Long.valueOf(String), though, they both map to the value 1. They are no longer distinct keys. This isn't going to break anything if you create a copy of the map and add the entries, because any duplicate keys will overwrite the previous entry for that key. A lazily transformed Map, though, would have no way of enforcing unique keys and would therefore break the contract of a Map.

ColinD
  • 108,630
  • 30
  • 201
  • 202
40

UPDATE for Java 8

You can use streams to do this:

Map<Long, String> newMap = oldMap.entrySet().stream()
  .collect(Collectors.toMap(e -> Long.parseLong(e.getKey()), Map.Entry::getValue));

This assumes that all keys are valid string-representations of Longs. Also, you can have collisions when transforming; for example, "0" and "00" both map to 0L.


I would think that you'd have to iterate over the map:

Map<Long, String> newMap = new HashMap<Long, String>();
for(Map.Entry<String, String> entry : map.entrySet()) {
   newMap.put(Long.parseLong(entry.getKey()), entry.getValue());
}

This code assumes that you've sanitized all the values in map (so no invalid long values).

I'm hoping there is a better solution.

EDIT

I came across the CollectionUtils#transformedCollection(Collection, Transformer) method in Commons Collection-Utils that looks like it might do what you want. Scratch that, it only works for classes that implement Collection.

Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
  • @Falcon :) Even if there was a transform method, the internal implementation would have to be something similar. i.e, `O(n)`. I can't think of any way you could do this such that it is better than `O(n)`. – Vivin Paliath Apr 20 '11 at 15:57
  • 2
    It's not about the complexity but if something is already written or can be easily achieved with guava's Functional approach (I already have Function written) and of course with comparable speed/efficiency then I don't need to do this. – Premraj Apr 20 '11 at 16:01
  • @Falcon, that was going to be my next comment :) I wish there was a *functional* or more elegant way of doing this :) – Vivin Paliath Apr 20 '11 at 16:01
26

You can now use Java 8 stream, map, collect to do this in a more readable, clean manner.

Map<String, String> oldMap

Map<Long, String> newMap = oldMap.entrySet().stream()
  .collect(Collectors.toMap(entry -> Long.parseLong(entry.getKey()), Map.Entry::getValue));
Dungeon Hunter
  • 19,827
  • 13
  • 59
  • 82
Vibha Rathi
  • 881
  • 8
  • 4
4

Here's an updated version of one of the answers below to make the resulting Map unmodifiable (no, it's not using Guava, just plain Java 8):

  import static java.util.stream.Collectors.collectingAndThen;
  import static java.util.stream.Collectors.toMap;

  ...

  newMap = oldMap.entrySet().stream().collect(collectingAndThen(
              toMap((Map.Entry<String, String> entry) -> transformKey(entry.getKey()),
                    (Map.Entry<String, String> entry) -> transformValue(entry.getValue())),
              Collections::unmodifiableMap)));
Evgeny Goldin
  • 1,860
  • 2
  • 15
  • 16
2

The short answer is no, Guava does not provide this one out of the box.

The simple way would be something like below. There are some cautions, however.

    public static <K, V, L, W> Map<L, W> transformMap(Map<K, V> map, Function<K, L> keyFunction, Function<V, W> valueFunction) {
    Map<L, W> transformedMap = newHashMap();

    for (Entry<K, V> entry : map.entrySet()) {
        transformedMap.put(
                keyFunction.apply(entry.getKey()),
                valueFunction.apply(entry.getValue()));
    }

    return transformedMap;
}

public static <K, V, L> Map<L, V> transformKeys(Map<K, V> map, Function<K, L> keyFunction) {
    return transformMap(map, keyFunction, Functions.<V>identity());
}

Guava's transformers are all "lazy" or view-based. I think to implement a map key transformer, you'd want a two-way function. My understanding is that there is a Converter that is in the works by the Guava team, which would solve that problem.

The other problem you run into is that you'd have to deal with the possibility of duplicates in order to be "Jimmy-proof", another Guava principle. One way to handle that would be to return a Multimap; another would be to throw an exception when you encounter duplicates. What I would not suggest is hiding the problem e.g. by ignoring subsequent entries with duplicate keys, or by overwriting the new entry with the duplicate key.

Community
  • 1
  • 1
Ray
  • 4,829
  • 4
  • 28
  • 55
0

Answer using Java 8 and jOOλ

private static <K1, K2, V> Map<K2, V> mapKeys(Map<K1, V> map, Function<K1, K2> keyMapper) {
    return Seq.seq(map).map(t -> t.map1(keyMapper)).toMap(Tuple2::v1, Tuple2::v2);
}
J. Dimeo
  • 262
  • 2
  • 10