Java Map.getOrDefault with bounded wildcard - java

Got a Map<String, ? extends Map<String, Integer>> mapOfMaps variable.
Map<String, Integer> result = mapOfMaps.get("aaa");
works, but
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",Collections.emptyMap());
says
The method getOrDefault(Object, capture#1-of ? extends Map<String,Integer>) in the type Map<String,capture#1-of ? extends Map<String,Integer>> is not applicable for the arguments (String, Map<String,Integer>)
the same goes for
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",Collections.<String,Integer>emptyMap());
or
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",(Map<String,Integer>)Collections.EMPTY_MAP);
or even
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",new HashMap<String, Integer>());
Is there a way of using the getOrDefault like that or do I have to use the clunky way ?
Map<String, Integer> result = mapOfMaps.get("aaa");
if( result == null ) {
result = Collections.emptyMap();
}

You can use Collections.unmodifiableMap to view your map as Map<String, Map<String, Integer>>.
Map<String, ? extends Map<String, Integer>> mapOfMaps = new HashMap<>();
Map<String, Map<String, Integer>> view = Collections.unmodifiableMap(mapOfMaps);
Map<String, Integer> map = view.getOrDefault("foo", Collections.emptyMap());
In a single line, however, it still looks ugly, since you need to specify the generic type arguments for unmodifiableMap.
Map<String, Integer> map = Collections.<String, Map<String, Integer>>
unmodifiableMap(mapOfMaps).getOrDefault("foo", Collections.emptyMap());
Explanation
You cannot call any method that has an unbounded or extends-bounded wildcard parameter, because the exact type of the wildcard is not known at compile time.
Let's make this simpler and look at Map<String, ? extends Number>, to which you could assign either of
Map<String, ? extends Number> map = new HashMap<String, Integer>();
Map<String, ? extends Number> map = new HashMap<String, Double>();
However, when calling map.getOrDefault(Object k, V defaultValue), there is no way to determine the type for defaultValue at compile time, since the actual type may change at runtime, even for the very same assignment (not the same instance though).
// compile-time error, could require a Double or any other Number-type
Number i = map.getOrDefault("foo", (Number)Integer.MAX_VALUE);

One possible, but still rather clunky, solution is a helper function:
static <K1, K2, V, M extends Map<K2, V>> Map<K2, V> getOrEmpty(Map<K1, M> mapOfMaps, K1 key) {
Map<K2, V> submap = mapOfMaps.get(key);
return submap != null ? submap : Collections.emptyMap();
}
and then call it like
Map<String, Integer> result = getOrEmpty(mapOfMaps,"aaa");
But I would still prefer a solution without having to define an extra function.

Related

Nested HashMaps and Declaration

I'm trying to experiment with Maps and I have this doubt:
Map<String, Object> input = new LinkedHashMap<String, Object>();
String operator = "in";
String argument = "foo";
String field = "AvailabilityStatus";
Map<String, Object> innerMap = new LinkedHashMap<String, Object>();
innerMap.put(operator, argument);
input.put(field, innerMap);
The function call for the above code is
String output = FunctionA(input);
Seems to work fine but changing the input to:
Map<String, Map<String, Object>> input = new LinkedHashMap<String, LinkedHashMap<String, Object>>();
doesn't let me call the function the same way. The functionA is:
public static String FunctionA(Map<String, Object> filters) throws Throwable {
//logic goes here
}
Aren't the two statements essentially trying to do the same thing?
Alternately, you could make the FunctionA method like this:
public static String FunctionA(Map<String, ? extends Object> filters) throws Throwable{
//logic goes here
}
Doing this will be happy then!
FunctionA(new HashMap<String, LinkedHashMap<String, Object>>());
A Map<String, Map<String, Object>> is not a subtype of Map<String, Object>, even though Map<String, Object>is a subtype of Object.
Indeed, uou can store whatever object you want in the latter, whereas you can only store Map<String, Object> instances in the former. That's why the compiler doesn't let you pass a Map<String, Map<String, Object>> to a method taking a Map<String, Object> as argument.
If it let you do that, the method could store Strings, Integers or Bananas into the map, which would thus ruin the type-safety of the map, supposed to only contain instances of Map<String, Object>.

How to change HashMap<K, V> value type from Object to String?

What is the easiest/best way to convert
Map<String, Object>
to
HashMap<String, String>
The API I am using has methods that return a Map but it would be easier if I didn't have to cast the Object to a String each time.
Also, is this even worth doing? Would a HashMap be faster/more efficient than a Map?
I'm assuming I'll have to loop through the original Map and copy the values to the new HashMap.
Thanks in advance!
You can use the constructor as others mentioned:
Map<String, String> newMap = new HashMap(oldMap);
This will only work however if you know that the Objects in question are really Strings.
but there is something I should mention:
Do not confuse interfaces with classes. Map is just an interface; a contract which contains only definitions. A class on the other hand is a concrete implementation of an interface. So it does not make any difference in terms of perfomrance if you use the Map interface or its runtime type (HashMap). It can make a difference however if you swap the implementations (to TreeMap for example).
Edit:
Here is the verbose solution which is liked by EE guys (no casting/rawtypes warning involved):
public class MapConverter {
public Map<String, String> convert(Map<String, Object> oldMap) {
Map<String, String> ret = new HashMap<String, String>();
for (String key : oldMap.keySet()) {
ret.put(key, oldMap.get(key).toString());
}
return ret;
}
}
Using the copy constructor on raw types works:
HashMap<String, String> hashMap = new HashMap(map);
However, the solution is ugly as the type system is ignored.
EDIT1:
When you execute
public static void main(String[] args) throws IllegalArgumentException,
InterruptedException, IOException {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("Bla", new Object());
HashMap<String, String> hashMap = new HashMap(map);
System.out.println(hashMap.get("Bla").getClass());
}
you get the class cast exception:
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
It is thrown when "System.out.println(hashMap.get("Bla").getClass());" is executed.
Consequently, the casts are actually delayed.
EDIT2:
You can avoid the copy with
HashMap<String, String> hashMap = (HashMap)map;
However, the problem remains the same as the following code shows:
public static void main(String[] args) throws IllegalArgumentException,
InterruptedException, IOException {
HashMap<String, Object> oldMap = new HashMap<String, Object>();
oldMap.put("Bla", new Object());
HashMap<String, String> hashMap = (HashMap)oldMap;
System.out.println(hashMap.get("Bla").getClass());
}
It behaves like the other example above in EDIT1.
EDIT3:
What about using a lambda?
Map<String, Object> map = new HashMap<String, Object>();
// 1
final Stream<Map.Entry<String, Object>> entries = map.entrySet()
.stream();
final Function<Map.Entry<String, Object>, String> keyMapper = (
Map.Entry<String, Object> entry) -> entry.getKey();
final Function<Map.Entry<String, Object>, String> valueMapper = (
Map.Entry<String, Object> entry) -> {
final Object value = entry.getValue();
if (value instanceof String) {
return (String) value;
} else {
throw new ClassCastException("Value '" + value + "' of key '"
+ entry.getKey() + "' cannot be cast from type "
+ ((value != null) ? value.getClass().getName() : null)
+ " to type " + String.class.getName());
}
};
final BinaryOperator<String> duplicateHandler = (key1, key2) -> {
throw new IllegalStateException(String.format("Duplicate key %s",
key1));
};
final HashMap<String, String> hashMap = entries.collect(Collectors
.toMap(keyMapper, valueMapper, duplicateHandler, HashMap::new));
System.out.println(hashMap);
If map only has string-to-string entries, it will copy them all.
E.g. Insert
map.put("aKey", "aValue");
at comment 1. It will print
{aKey=aValue}
which is fine.
If you have at least one string-to-non-string entry in your map, copying will fail.
E.g. Insert
map.put("aKey", 42);
at comment 1. It will print
Exception in thread "main" java.lang.ClassCastException: Value '42' of key ' aKey' cannot be cast from type java.lang.Integer to type java.lang.String
at ...
which shows the string-to-non-string entry.
I know this solution is not so simple but it is safe.
If you know the types of key and value (like <String, String>), you can just cast the whole map:
Map<String, String> newMap = (HashMap<String, String>)oldMap;
If you need a separate Map instance, you can use the constructor of HashMap like this:
HashMap<String, String> = new HashMap<String, String>((HashMap<String, String>) oldMap);

How to receive difference of maps in java?

I have two maps:
Map<String, Object> map1;
Map<String, Object> map2;
I need to receive difference between these maps. Does exist may be apache utils how to receive this difference?
For now seems need take entry set of each map and found diff1 = set1 - set2 and diff2 = set2- set1.
After create summary map =diff1 + diff2
It looks very awkwardly. Does exist another way?
Thanks.
How about google guava?:
Maps.difference(map1,map2)
Here is a simple snippet you can use instead of massive Guava library:
public static <K, V> Map<K, V> mapDifference(Map<? extends K, ? extends V> left, Map<? extends K, ? extends V> right) {
Map<K, V> difference = new HashMap<>();
difference.putAll(left);
difference.putAll(right);
difference.entrySet().removeAll(right.entrySet());
return difference;
}
Check out the whole working example
If I understood well you are trying to calculate symmetric difference beetween the two maps entry sets.
Map<String, Object> map1;
Map<String, Object> map2;
Set<Entry<String, Object>> diff12 = new HashSet<Entry<String, Object>>(map1.entrySet());
Set<Entry<String, Object>> diff21 = new HashSet<Entry<String, Object>>(map2.entrySet());
Set<Entry<String, Object>> result;
diff12.removeAll(map2.entrySet());
diff21.removeAll(map1.entrySet());
diff12.addAll(diff21);
Considering the awkward behavior you mentioned, let's take a closer look at the above code behavior. For example if we take the numerical example from the above given link:
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("a", 1);
map1.put("b", 2);
map1.put("c", 3);
map1.put("d", 4);
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("a", 1);
map2.put("d", 4);
map2.put("e", 5);
After you calculate the difference as shown, the output:
System.out.println(Arrays.deepToString(diff12.toArray()));
gives:
[e=5, c=3, b=2]
which is the correct result. But, if we do it like this:
public class CustomInteger {
public int val;
public CustomInteger(int val) {
this.val = val;
}
#Override
public String toString() {
return String.valueOf(val);
}
}
map1.put("a", new CustomInteger(1));
map1.put("b", new CustomInteger(2));
map1.put("c", new CustomInteger(3));
map1.put("d", new CustomInteger(4));
map2.put("a", new CustomInteger(1));
map2.put("d", new CustomInteger(4));
map2.put("e", new CustomInteger(5));
the same algorithm gives the following output:
[e=5, a=1, d=4, d=4, b=2, a=1, c=3]
which is not correct (and might be described as awkward :) )
In the first example the map is filled with int values wich are automatically boxed to Integer values.
The class Integer has its own implementation of equals and hashCode methods.
The class CustomInteger does not implement these methods so it inherits them from the omnipresent Object class.
The API doc for the removeAll method from the Set interface says the following:
Removes from this set all of its elements that are contained in the specified collection
(optional operation). If the specified collection is also a set, this operation effectively modifies this set so that its value is the asymmetric set difference of the two sets.
In order to determine which elements are contained in both collections, the removeAll method uses the equals method of the collection element.
And that's the catch: Integer's equals method returns true if the two numeric values are the same, while Object's equals method will return true only if it is the same object, e.g.
:
Integer a = 1; //autoboxing
Integer b = new Integer(1);
Integer c = 2;
a.equals(b); // true
a.equals(c); // false
CustomInteger d = new CustomInteger(1);
CustomInteger e = new CustomInteger(1);
CustomInteger f = new CustomInteger(2);
d.equals(e); //false
d.equals(f) // false
d.val == e.val //true
d.val == f.val //false
If it's still a bit fuzzy I strongly suggest reading the following tutorials:
Learning the Java language
Collections
Set<Entry<String, Object>> diff = new HashSet<Entry<String, Object>>((map1.entrySet()));
diff.addAll(map2.entrySet());//Union
Set<Entry<String, Object>> tmp = new HashSet<Entry<String, Object>>((map1.entrySet()));
tmp.retainAll(map2.entrySet());//Intersection
diff.removeAll(tmp);//Diff
Building on Vlad's example to work with maps of different sizes
public static <K, V> Map<K, V> mapDiff(Map<? extends K, ? extends V> left, Map<? extends K, ? extends V> right) {
Map<K, V> difference = new HashMap<>();
difference.putAll(left);
difference.putAll(right);
difference.entrySet().removeAll(left.size() <= right.size() ? left.entrySet() : right.entrySet());
return difference;
}
Try using guava's MapDifference.
Simple way to do it. if you want complex way, you can change filter to compare value.
Map<String, Object> map1 = new HashMap<String, Object>() {{
put("A", "1");
put("B", "2");
put("C", "3");
}};
Map<String, Object> map2 = new HashMap<String, Object>() {{
put("A", "1");
put("B", "2");
put("D", "3");
}};
Map<String, Object> newList = map1.keySet().stream().filter(str -> !map2.containsKey(str)).collect(Collectors.toMap(v -> v, v -> map1.get(v)));
Map<String, Object> oldList = map2.keySet().stream().filter(str -> !map1.containsKey(str)).collect(Collectors.toMap(v -> v, v -> map2.get(v)));
System.out.println(newList);
System.out.println(oldList);

Generic function for Map of maps in Java

I am trying to write a generic function which will accept both of the following data types
Map <Integer, Map<Integer, Long>>
Map <Integer, Map<Integer, Double>>
My function looks like this,
function(Map<Integer, Map<Integer, ? extends Number>> arg) {}
But I am getting an incompatible type error. It works for a Map, but not for map of Maps. I am not able to understand why? Is there any way to do this?
You could try something like
static <T extends Number> void function(Map<Integer, Map<Integer, T>> map) {}
public static void main(String[] args) {
Map<Integer, Map<Integer, Long>> map1 = new HashMap<Integer, Map<Integer, Long>>();
Map<Integer, Map<Integer, Double>> map2 = new HashMap<Integer, Map<Integer, Double>>();
Map<Integer, Map<Integer, String>> map3 = new HashMap<Integer, Map<Integer, String>>();
function(map1);
function(map2);
function(map3);// <-- compilation error here, String is not Number
}
First let's reduce the problem by using Sets instead:
Set<Set<Long>> longSetSet = null;
Set<Set<Double>> doubleSetSet = null;
Set<Set<? extends Number>> someNumberSetSet;
// try assigning them
someNumberSetSet = longSetSet; //
someNumberSetSet = doubleSetSet; // compiler errors - incompatible types
At first glance you might wonder why this assignment is illegal, since after all you can assign a Set<Long> to Set<? extends Number> The reason is that generics are not covariant. The compiler prevents you from assigning a Set<Set<Long>> to Set<Set<? extends Number>> for the same reason it won't let you assign a Set<Long> to a Set<Number>. See the linked answer for more details.
As a workaround, you can use a type parameter in your method signature as other answers have suggested. You can also use another wildcard to make the assignment legal:
Set<? extends Set<? extends Number>> someNumberSetSet;
someNumberSetSet = longSetSet; //
someNumberSetSet = doubleSetSet; // legal now
Or in your example:
function(Map<Integer, ? extends Map<Integer, ? extends Number>> arg) { }
Why not just parameterize the method?
public <T extends Number> void function(Map<Integer, Map<Integer, T>>) { ... }
I've found that the wildcard capture tends to confuse people as to what it really does.
Map<Integer, ? extends Number> really means any Map whose key is Integer and whose value is a type derived from Number. This means Map<Integer, Integer>, Map<Integer,Long>.
For this reason, you can never really add to those collections, because of the wildcard the compiler can't tell what the real type is in order to add.
static void test(Map<Integer, Map<Integer, ? extends Number>> a) { }
This actually works just fine for me (JavaSE-1.6).

Map<String, Map<String, Boolean>> myMap = new HashMap<String,HashMap<String,Boolean>>();

Why doesn't that work in java, but this does
Map<String, Map<String, Boolean>> myMap = new HashMap<String,Map<String,Boolean>>();
Just to clarify the below alteration of the nested HashMap shows a compiler error, whereas the above does not not; with a Map (not hashmap)
Map<String, Map<String, Boolean>> myMap = new HashMap<String,HashMap<String,Boolean>>();
This is because generics in Java are invariant, i.e. even if class B is an A, a Collection<B> is not a Collection<A>.
And this is for a good reason. If your example were legal, this would be possible:
Map<String, HashMap<String, Boolean>> myHashMap = new HashMap<String,HashMap<String,Boolean>>();
Map<String, Map<String, Boolean>> myMap = myHashMap;
myMap.put("oops", new TreeMap<String, Boolean>());
HashMap<String, Boolean> aHashMap = myMap.get("oops"); // oops - ClassCastException!
In the second case myMap is a map which keys are of type String and values are of type Map<String, Boolean>. HashMap<String, Boolean> is not a Map<String, Boolean> it implements it. Therefore, this will compile:
Map<String, ? extends Map<String, Boolean>> myOtherMap =
new HashMap<String,HashMap<String,Boolean>>();
I think that's because of the difference between Map<String, Boolean> and HashMap<String,Boolean>.
Indeed, the generics are here a specification, which must be the same on both sides. (or at least that's my opinion).

Categories