Generic function for Map of maps in Java - 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).

Related

Java Map.getOrDefault with bounded wildcard

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.

Why List<Map<? extends Number,? extends Number>> is not working for List<Map<Integer,Double>>

I am iterating through a map whose keys are charts and values are data sets which will be displayed on charts. Data sets are lists of maps because I have multiple XYSeries displayed on each of my Charts (one series - one map with x and y values). In some charts x-axis/y-axis values are Doubles, and in others Integers. Thus, my data sets are type < ? extends Number>. What am I doing wrong?
for (Map.Entry<Chart, List<Map<? extends Number, ? extends Number>>> entry : tcInstance.getMapChartDataset().entrySet()) {
switch (entry.getKey().getTitle()) {
case something:
entry.setValue(listOfMaps1);
break;
case something else:
entry.setValue(listOfMaps2);
break;
// other case options
}
}
These are the declarations of lists of maps:
static List<Map<Integer, Double>> listOfMaps1 = new ArrayList<>();
static List<Map<Double, Double>> listOfMaps2 = new ArrayList<>();
I expected values to be set, but instead I got these errors which tell that the method setValue is not applicable for the arguments (List>) (and the same error for the arguments (List>).
A List<Map<Integer,Double>> is not a List<Map<? extends Number,? extends Number>>.
If it were, I could do this:
List<Map<Integer,Double>> list = new ArrayList<>();
List<Map<? extends Number,? extends Number>> listX = list; // Doesn't work, pretend it does.
Map<Double,Integer> map = new HashMap<>();
map.put(0.0, 0);
listX.add(map);
for (Map<Integer, Double> e : list) {
Integer i = e.keySet().iterator().next(); // ClassCastException!
}
You would get a ClassCastException because e has a Double key, not Integer as expected.
Ideone demo
If you add an extra upper bound to the wildcarded list:
List<? extends Map<? extends Number,? extends Number>>
^-------^ here
then you wouldn't have been able to add map to listX, so it would be safe.

Map<String, ? super Number> for adding and Map<String, ? extends Number> for printing is producing errors

I have read through several posts on here about this topics.
This was a especially a good post
I thought I understood the PECS-concept, and tried to set up a small example to test it out.
import java.util.Map;
import java.util.HashMap;
class Test {
public static void main(String[] args) {
Map<String, ? super Number> map = new HashMap<>();
map.put("int", 1);
map.put("double", 1.0);
map.put("long", 100000000000L);
print(map);
}
public static void print(Map<String, ? extends Number> map) {
map.forEach((k, v) -> System.out.printf("Key: %s, Val: %s%n", k, v));
}
}
I know I need to use super to be able to insert multiple subtypes in to the same map.
But when it comes to printing. I thought using extends would be sufficient, since PECS (Producer extends)
Instead I get this:
Error:(12, 15) java: incompatible types: java.util.Map<java.lang.String,capture#1 of ? super java.lang.Number> cannot be converted to java.util.Map<java.lang.String,? extends java.lang.Number>
In both cases Number suffices and is most adequate.
As said super does not make sense; you could just as well have written Object.
Map<String, Number> map = new HashMap<>();
With this you may put a Number or a child of Number into the map.
Or get a Number or a parent of Number from the map.
Now if you would use:
public static void print(Map<String, ? extends Number> map) {
you cannot put a Double (or whatever) into that map as the actual map argument could have been a Map<String, Integer>. Hence Map<String, Number>.
As the type system of java is not very strong/expressive, a good rule is to keep extends for meta level constructs (=when you need them). For simple data structures PECS follows from the data flow.
This is because
Map<String, ? super Number> map
may contain any values, try this
Map<String, Object> map1 = new HashMap<>();
map1.put("1", "2");
Map<String, ? super Number> map = map1;
there is no compile error

What is wrong with my method generic parameters

I want to populate a List with generic maps, but my code does not compile. I have prepared the most simplified example for the problem. In the comments above problematic lines I have put the error the line below produces.
void populateList(List<? extends Map<String,?>> list) {
list.clear();
HashMap<String, ?> map;
map = new HashMap<String,String>();
//The method put(String, capture#2-of ?) in the type HashMap<String,capture#2-of ?> is not applicable for the arguments (String, String)
map.put("key", "value"); // this line does not compile
// The method add(capture#3-of ? extends Map<String,?>) in the type List<capture#3-of ? extends Map<String,?>> is not applicable for the arguments (HashMap<String,capture#5-of ?>)
list.add(map); //This line does not compile
}
Why is this so? Is there something I do not understand?
EDIT 1
According to one of the answers below in which he pointed out that ? stands for unknown type and not a descendant of Object. This is a valid point. And also, inside the method I know the type which go into map so I have modified my simple code accordingly.
void populateList(List<? extends Map<String,?>> list) {
list.clear();
HashMap<String, String> map; //known types
map = new HashMap<String,String>();
map.put("key", "value"); // this line now compiles
// The method add(capture#3-of ? extends Map<String,?>) in the type List<capture#3-of ? extends Map<String,?>> is not applicable for the arguments (HashMap<String,capture#5-of ?>)
list.add(map); //This line STILL does not compile. Why is that?
}
The reason I am asking this is because a method form android SDK expects such list and as it seems one cannot populate such lists. How does one do that? Typecast?
EDIT 2
Since there several proposals to change my signature I will add that I cannot do that. Basicaly, I would like to populate lists for SimpleExpandablaListAdapter.
void test() {
ExpandableListView expandableListView.setAdapter(new ArrayAdapterRetailStore(this, R.layout.list_item_retail_store, retailStores));
List<? extends Map<String, ?>> groupData= new ArrayList<HashMap<String,String>>();
populateGroup(groupData)
// child data ommited for simplicity
expandableListView.setAdapter( new SimpleExpandableListAdapter(
this,
groupdata,
R.layout.list_group,
new String[] {"GroupKey"},
new int[] {R.id.tvGroupText},
childData,
R.layout.list_item_child,
new String[] {"ChildKey"},
new int[] {R.id.tvChilText}));
}
// I want populateGroupData() to be generic
void populateGroupData(List<? extends Map<String,?>> groupData) {
groupData.clear();
HashMap<String,String> map;
map = new HashMap<String,String>();
map.put("key", "value");
groupData.add(map); // does not compile
}
From the documentation
When the actual type parameter is ?, it stands for some unknown type. Any parameter we pass to add would have to be a subtype of this unknown type. Since we don't know what type that is, we cannot pass anything in. The sole exception is null, which is a member of every type.
so, you can add only
list.add(null);
Please read this tutorial on Generics Wildcards
here is the working code
//also works with void populateList(List<Map<String,?>> list) {
void populateList(List<? super Map<String,?>> list) {
list.clear();
Map<String, String> map;
map = new HashMap<String,String>();
map.put("key", "value"); // this line now compiles
list.add(map); //This line compiles
}
and why it works:
List<? super Map<String,?>> list or simply List<Map<String,?>> list
// => this ensure you that the list can contains a Map<String,?>.
Map<String, String> map is a Map<String,?>
// =>that can ber inserted to the list, so you don't need any cast
Edit:
The common mistake is that the wildcard "? extends Map" will limit the function call to a list that is "at least" typed with map. This is not what you want, because you could pass a List<TreeMap<String,?>>which can not contain a HashMap for example. Additionnaly you couldn't call your method with a List<Object>
-> To illustrate generics limitation i have added 2 examples with super and extends
void exampleWithExtends(List<? extends Map<String,?>> list) {
}
void exampleWithSuper(List<? super Map<String,?>> list) {
}
void funWithGenerics(){
exampleWithExtends(new ArrayList<TreeMap<String,String>>());
exampleWithExtends(new ArrayList<Map<String,?>>());//works in both cases
//exampleWithExtends(new ArrayList<Object>()); /does not compile
//exampleWithSuper(new ArrayList<TreeMap<String,String>>()); //does not compile
exampleWithSuper(new ArrayList<Map<String,?>>());//works in both cases
exampleWithSuper(new ArrayList<Object>());
}
There is no way you can write Map<String, String> map = getMap("abc"); without a cast
The problem has more to do with easymock and the types returned/expected by the expect and andReturn methods, which I'm not familiar with. You could write
Map<String, String> expected = new HashMap<String, String> ();
Map<?, ?> actual = getMap("someKey");
boolean ok = actual.equals(pageMaps);
//or in a junit like syntax
assertEquals(expected, actual);
Not sure if that can be mixed with your mocking stuff. This would maybe work:
EasyMock.expect((Map<String, String>) config.getMap("sillyMap")).andReturn(pageMaps);
Also note that you can't add anything to a generic collection with a wildcard. So this:
Map<?, ?> map = ...
map.put(a, b);
won't compile, unless a and b are null
Java is type-safe! At least at this point :)
This will do the trick:
HashMap<String, String> map = new HashMap<String,String>();
map.put("key", "value");
((List<Map<String,String>>)groupData).add(map);

Generic method is not applicable for the arguments

Sorry for the yet another "Java generic method is not applicable" question. I would like to know what am I missing in my understanding:
List<E> is a subtype of Collection<E>
--> meaning, List<String> is a subtype of Collection<String>
Suppose A extends B, List<A> is not a subtype of List<B>
--> but in this case, there's only one type T (or String), so I don't see how the Substitution Principle can explain my problem?
Problem Code:
private <T, K> void genericAddToMapOfLists(HashMap<K, Collection<T>> mapOfLists,
K key, T value) {
if (mapOfLists.containsKey(key)) {
mapOfLists.get(key).add(value);
} else {
List<T> newList = new ArrayList<T>();
newList.add(value);
mapOfLists.put(key, newList);
}
}
private void parseToFruitList(HashMap<String, List<String>> fruit_colors,
String fruitName) {
String color = "";
genericAddToMapOfLists(fruit_colors, fruitName, color);
}
Error:
The method genericAddToMapOfLists(HashMap<K,Collection<T>>, K, T) in the type MyGroceryStore is not applicable for the arguments (HashMap<String,List<String>>, String, String)
The code works when I change the method signature to genericAddToMapOfLists(HashMap<K,List<T>>, K, T).
This is exactly the problem you are explaining in your second point.
Suppose A extends B, List<A> is not a subtype of List<B>
In this case your method expects
HashMap<?, Collection<?>>
but you are giving it
HashMap<?, List<?>>
List extends Collection, but HashMap<?, List> is not a subtype of HashMap<?, Collection>
(I'm not using ? as a wildcard, we just don't care about it right now)
You're right in that "List<String> is a subtype of Collection<String>". And if A extends B, List<A> is not a subtype of List<B>.
Taking that one step further, a HashMap<String, List<String>> is not a HashMap<String, Collection<String>>.
The same reasoning applies, where A is List and B is Collection. If a HashMap<String, List<String>> was a HashMap<String, Collection<String>>, then you could put a Vector<String> into a HashMap<String, List<String>> by assigning it to a HashMap<String, Collection<String>>, even though a Vector isn't a List, and so it's not allowed.

Categories