2

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 
}
  • Don't forget to mark the answer which solves your problem. – Paul Wasilewski Apr 10 '14 at 13:08
  • You can add your Map with a cast see my answer (http://stackoverflow.com/a/22988021/1405363) or look at pdem answer (http://stackoverflow.com/a/22988741/1405363) if you can change the method signature. – Paul Wasilewski Apr 10 '14 at 13:12
  • Hi, take a look at my answer http://stackoverflow.com/a/22988021/1405363 - you have to cast groupData. This is still not Android specific! – Paul Wasilewski Apr 10 '14 at 18:05

4 Answers4

6

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

Keerthivasan
  • 12,760
  • 2
  • 32
  • 53
3

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>());
}
pdem
  • 3,880
  • 1
  • 24
  • 38
  • Thank you for your respone. but my List should be limited to be at least typed with Map or its derived classes. –  Apr 10 '14 at 13:56
  • so you can pass exactly a `List>` without extends or super – pdem Apr 10 '14 at 14:03
  • But I want my method to be generic for all descendants of Map, not just Map. Map is actually an interface. –  Apr 10 '14 at 16:06
  • 1
    Why don't you want a list typed Map like in my first line of example `//... void populateList(List> list) {`? Do you know that the list typed Map can contain a HashMap (and every instance of Map) ? it will be generic as you just want. If you want, i can add an example to my post to explain why it cannot work with extends. – pdem Apr 11 '14 at 09:09
  • Maybe i found a best way to explain it: `List extends Map>`doesn't mean a List whose element can extends Map, It means a List that is restricted to a type that subclass a Map. _________________ So you don't have the garantee that the list can contain the type of Map that you want, because a caller could pass you a `List` which is really an instance of `List extends Map>` but can not contain a HasMap because HashMap doesn't extends TreeMap. – pdem Apr 11 '14 at 09:26
0

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);
Paul Wasilewski
  • 9,762
  • 5
  • 45
  • 49
0

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

kAnNaN
  • 3,669
  • 4
  • 28
  • 39