Generic HashMap - java

I have one method, check which has two hashmaps as parameters. Keys of these maps is a String and value is String or Arraylist.
Which is the better solution:
public static boolean check(HashMap<String, ?> map1, HashMap<String, ?> map2) {
for ( entry <String, ? > entry : map1.entryset()) {
...
}
}
or
public static <V> boolean check(HashMap<String, V> map1, HashMap<String, V> map2) {
for ( entry <String, V > entry : map1.entryset()) {
...
}
}
and why?
And can you also give me some more information about the difference between these two solutions?

In the first, the ? coul dbe anything. One could be <String, String> the other could be <String, Double>. In the second option they must be the same.
Now the first is acceptable as long as you have the ability to convert them so they're comparable. For example, you could do .toString() on both values to compare. But personally, I would prefer the second as it allows me to have more control over what's going on, and gives me compile time checking of types.

The second one enforces at compile-time that the two maps are parameterised the same as each other. It also allows you do something useful with the maps, such as inserting non-null elements into them (this isn't possible with wildcards).

Related

How to get the specific <K,V> entry based on the given key in Java HashMap?

Ex. I have the following HashMap, and how to get the entry 'b'=6 if I know the key 'b'. Is there any way to do it?
Map<Character, Integer> map=new HashMap<>();
map.put('a',7);
map.put('b',6);
map.put('c',5);`
By the way, I want to do this is because all those entries are in a priority queue. I have to remove that entry from the priority queue and re-insert it to make sure it is in order.
Thanks.
If you know the key, simply get the value using Map#get(Object). As long as you know both, you have the entry. The Map interface doesn't provide a specific method that returns a certain entry.
Map<Character, Integer> map = ...
Character key = 'b';
Integer value = map.get(key);
// now with the 'key' and 'value' that make TOGETHER an entry.
If you really somehow need an Entry<Character, Integer> construct it like this:
Map.Entry<Character, Integer> entry = new SimpleEntry<>(key, value);
There is no better way. One would say you can use Stream API to iterate through the entries and return the first one found, however, you lose the main benefit of the HashMap which is the constant-time look-up.
// DON'T DO THIS!
Entry<Character, Integer> entry = map.entrySet().stream()
.filter(e -> key.equals(e.getKey()))
.findFirst()
.orElse(null);
You could do something like this
public Entry<Character, Integer> entry_return(Map<Character, Integer> map) {
for(Map.Entry<Character, Integer> entry : map.entrySet()) {
if(entry.getKey() == 'b')
return entry;
}
}
or use a stream API if you really need the entry but i don't know if that is very common/usefull
Since Java 9, you can use static method Map.entry​(K k, V v), which:
returns an unmodifiable Map.Entry containing the given key and value.
So, you can obtain your Entry<K, V> instance, as:
Map.Entry<Character, Integer> entry = Map.entry(key, map.get(key));
where map stores a reference to your Map<K, V> instance.

Sort Hashmap values compareTo method

I am writing the below code to sort the hash map values :
private static HashMap sortByValues(HashMap map) {
List list = new LinkedList(map.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return (((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue());
}
});
}
However When I execute this, it throws an error stating cannot find symbol compareTo. But isnt the method in the String class which is in the lang package?
Also when i replace it by adding a Comparable typecast, it runs fine
private static HashMap sortByValues(HashMap map) {
List list = new LinkedList(map.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return ((Comparable) ((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue());
}
});
}
Can someone please help, I am a beginner in Collections.
All of Java's collection classes and interfaces are generics, which means they are intended to be used with type parameters. For historical reasons it's possible to use them without type parameters, which is what you've done here. However, it's a bad idea to do that, because
you sacrifice a lot of the type safety that the compiler gives you,
you end up casting objects to whatever type you need them to be, which is prone to error, and such errors usually only reveal themselves at run time.
Finding errors at compile time is better than finding them at run time. The compiler is your friend - it gives you messages to help you find your errors.
Now in this particular case, you seem to be building a sorted list of values from your map. But it only makes sense to do this if the values in your map belong to some type that can be sorted. There's no general way of sorting Object, so for this to make sense, you want to restrict your parameter to be a map whose values can be sorted, or to put it another way, can be compared to other objects of the same type.
The generic interface that tells you that one object can be compared to another is Comparable. So if you have a type V, then writing V extends Comparable<V> means that one object of type V can be compared to other objects of type V, using the compareTo method. This is the condition that you want the type of the values in your map to obey. You don't need any such condition on the type of the keys in your map.
Therefore, you could write your method as generic, which means that its signature will list some type parameters, inside < > characters, possibly with some conditions on those type parameters. In your case, you'd give your method a signature like this, assuming it's going to return a List.
private static <K, V extends Comparable<V>> List<V> sortAndListValues(Map<K,V> map)
Of course, if you really intend to return some kind of sorted map, then it might be more like
private static <K, V extends Comparable<V>> Map<K,V> sortByValues(Map<K,V> map)
but you need to remember that it's not possible to sort HashMap objects. They're naturally sorted in an order that's implied by the hashCode function of the key class, and by the current size of the map. This is generally not a very useful order. There are other types of map in the JDK, such as
TreeMap, which sorts its entries according to the key - not what you want here
LinkedHashMap, which sorts its entries according to the order they were inserted - and you could probably make use of this here.
For the sake of answering your question though, I'm just going to write the List version of your method.
private static <K, V extends Comparable<V>> List<V> sortAndListValues(Map<K,V> map) {
List<V> toReturn = new LinkedList<>(map.values());
Collections.sort(toReturn, new Comparator<V>() {
public int compare(V first, V second) {
return first.compareTo(second);
}
});
return toReturn;
}
Note that by using the type parameters K and V wherever it's appropriate to do so, there's no need for any kind of casting. The compiler will also warn you if you try to use any of the objects in the map in a way that's inappropriate for their type.
There are shorter ways of writing this of course, using the "functional style" that comes with Java 8. But that's a topic for another post entirely.
#ghostrider - you have removed generics from HashMap so both key and value are of Object type. Inside contents of map are Comparable type but the reference is of Entry<Object, Object> not Entry<Object, Comparable>. Look into the below example.
Object obj = new Integer(5);
int i = obj.intValue(); // Error
int i = ((Integer)obj).intValue(); // Success
Here int i = obj.intValue(); fails but int i = ((Integer)obj).intValue(); get success because i am explicitly type casting because of reference is of Object type.
You can do this by following
private static Map<String, Integer> sortByValue(Map<String, Integer> unsortMap) {
// 1. Convert Map to List of Map
List<Map.Entry<String, Integer>> list =
new LinkedList<Map.Entry<String, Integer>>(unsortMap.entrySet());
// 2. Sort list with Collections.sort(), provide a custom Comparator
// Try switch the o1 o2 position for a different order
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1,
Map.Entry<String, Integer> o2) {
return (o1.getValue()).compareTo(o2.getValue());
}
});
// 3. Loop the sorted list and put it into a new insertion order Map LinkedHashMap
Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
for (Map.Entry<String, Integer> entry : list) {
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}

How to convert Guava HashMultmap to java.util.Map

I am trying to convert Guava Multimap<String ,Collection<String>> into Map<String, Collection<String>> but I get a syntax error when using Multimaps.asMap(multimap). Here is a code:
HashMultimap<String, Collection<String>> multimap = HashMultimap.create();
for (UserDTO dto : employees) {
if (dto.getDepartmentNames() != null) {
multimap.put(dto.getUserName().toString().trim(), dto.getDepartmentNames());
}
}
Map<String, Collection<String>> mapOfSets = Multimaps.asMap(multimap);
Here is a screenshot of error:
Can someone point out where I am doing a mistake?
Return type of Multimaps.asMap(multimap) is Map<String, <Set<Collection<String>>.
Multimap can hold multiple values of the same key. Hence, when you want to convert from multimap to a map, you need to keep collection of values for each key, just in case, there is a key which appears twice in the map.
If you want to convert from MultiMap to Map and make set sum on the values, you can do the following:
Multimaps.asMap(multimap).entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e->e.getValue().stream()
.flatMap(Collection::stream).collect(toSet())));
I think what you're doing here is using Multimap wrong. Multimap<String, Collection<String>> is roughly an equivalent to Map<String, Collection<Collection<String>>>, so it results in nested collections when using asMap view (ex. {user1=[[IT, HR]], user2=[[HR]], user3=[[finance]]}).
What you really want is to use Multimap<String, String> (more specifically: SetMultimap<String, String> which corresponds to Map<String, Set<String>>) and use Multimap#putAll(K, Iterable<V>):
SetMultimap<String, String> multimap = HashMultimap.create();
for (UserDTO dto : employees) {
if (dto.getDepartmentNames() != null) {
// note `.putAll` here
multimap.putAll(dto.getUserName().toString().trim(), dto.getDepartmentNames());
}
}
Map<String, Set<String>> mapOfSets = Multimaps.asMap(multimap);
// ex. {user1=[HR, IT], user2=[HR], user3=[finance]}
Using Multimaps#asMap(SetMultimap) instead of SetMultimap#asMap() is necessary due to Java type system limitation (can't override generic type in a subtype when its nested in a generic type):
Note: The returned map's values are guaranteed to be of type Set. To
obtain this map with the more specific generic type Map<K, Set<V>>,
call Multimaps.asMap(SetMultimap) instead.

Best way to map keys of a Map

I want to transform keys in a HashMap. The map has lower_underscore keys but an expected map should have camelCase keys. The map may also have null values.
The straightfoward code to do this is here:
Map<String, Object> a = new HashMap<String, Object>() {{
put("foo_bar", 100);
put("fuga_foga", null); // A value may be null. Collectors.toMap can't handle this value.
}};
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
I want to know the method to do this like Guava's Maps.transformValues() or Maps.transformEntries(), but these methods just transforms values.
Collectors.toMap() is also close, but this method throws NullPointerException when a null value exists.
Map<String, Object> collect = a.entrySet().stream().collect(
Collectors.toMap(x -> toCamel(x.getKey()), Map.Entry::getValue));
If you absolutely want to solve this using streams, you could do it like this:
Map<String, Object> b = a.entrySet()
.stream()
.collect(HashMap::new,
(m, e) -> m.put(toCamel(e.getKey()), e.getValue()),
HashMap::putAll);
But I find the "conventional" way shown in your question easier to read:
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
This is intended as a comment, but got too long for that.
Wanting something like Guava's Maps.transformValues() or Maps.transformEntries() doesn't make too much sense I think.
Those methods return a view of the original map and when you get some
value using a key then the value is transformed by some function that you specified.
(I could be wrong here because I'm not familiar with Guava but I'm making these assumptions based on documentation)
If you wanted to do "transform" the keys then you could do it by writing a wapper for the map like so:
public class KeyTransformingMap<K, V> implements Map {
private Map<K, V> original;
private Function<K, K> reverseTransformer;
public V get(Object transformedKey) {
K originalKey = reverseTransformer.apply((K) transformedKey);
return original.get(originalKey);
}
// delegate all other Map methods directly to original map (or throw UnsupportedOperationException)
}
In your case where you have a map with snake case keys but want camel case keys,
the reverseTransformer function would take in a camel case string and return a snake case string.
I.e reverseTransformer.apply("snakeCase") returns "snake_case" which you can then use as a key for the original map.
Having said all that I think that the straightforward code you suggested is the best option.

Java Map compiler error with generic

// I know that this method will generated duplicate
// trim keys for the same value but I am just
// trying to understand why we have a compile error:
// The method put(String, capture#11-of ?) in the type
// Map<String,capture#11-of ?> is not applicable for the arguments
// (String, capture#12-of ?)
void trimKeyMap(Map<String, ?> map){
for (String key : map.keySet()) {
map.put(StringUtils.trim(key), map.get(key)); // compile error
}
}
How come the value that we want to put map.get(key) can be from a different type?
The problem is that the compiler only knows the key type is "unknown", but doesn't know it's the same unknown type for the type of the Map's key and the type returned from get() (even though we as humans realize that it's the same).
If you want to make it work, you must tell the compiler it's the same unknown type by typing your method, for example:
void <V> trimKeyMap(Map<String, V> map) {
for (String key : map.keySet()) {
map.put(StringUtils.trim(key), map.get(key));
}
}
It might have been possible for the Java 5 expert group, to add a little more power to the specification of wildcards in generic method argument types. I suspect the enhancement didn't make it due to lack of time in the specs phase. See my own recent question here: How does the JLS specify that wildcards cannot be formally used within methods?
Short of a better solution, you have to make your method a generic method:
<V> void trimKeyMap(Map<String, V> map){
for (String key : map.keySet()) {
map.put(StringUtils.trim(key), map.get(key));
}
}
You cannot put anything in a Map<String, ?> type, except for the null literal:
Map<String, ?> map = new HashMap<String, String>();
map.put("A", null); // Works
map.put("B", "X"); // Doesn't work.
The compiler doesn't know that the map's value argument type was String. So it cannot allow you to add anything to the map, even if you got the value from the map itself.
You can use type inference in a helper method, if you want to keep the clean method signature (the client of trimKeyMap shouldn't have to use a generic method):
void trimKeyMap(final Map<String, ?> map) {
for (final String key : map.keySet()) {
trim(map, key);
}
}
private <T> void trim(final Map<String, T> map, final String key) {
map.put(StringUtils.trim(key), map.get(key));
}
This is called wildcard capture and is discussed more here: http://www.ibm.com/developerworks/library/j-jtp04298/
? is not a wildcard for any type but the unknown type. So the map only accepts ?-type objects and not String. One conclusion from this (rather strange) fact: We can't add values to collections that are parametized with ?.
? is different from Object.
A Map<String, ?> is a generic map where you don't know the element type, but it is still being enforced, i.e. it does not allow you to put anything.
You cannot add values (that are not null) to Collections and Maps parametrized with a wildcard ?. That is because a wildcard is not a substitute for any object, it's just an unknown object. So you cannot add any (lets put it that way) known object in a map that accepts only unknown.
You can only read values.

Categories